hopr_crypto_sphinx/
packet.rs

1use std::{
2    fmt::{Debug, Formatter},
3    marker::PhantomData,
4    ops::{Deref, DerefMut},
5};
6
7use hopr_crypto_types::{crypto_traits::StreamCipher, prelude::*};
8use hopr_primitive_types::prelude::*;
9use typenum::Unsigned;
10
11use crate::{
12    derivation::derive_packet_tag,
13    errors::SphinxError,
14    routing::{ForwardedHeader, RoutingInfo, SphinxHeaderSpec, forward_header},
15    shared_keys::{Alpha, GroupElement, SharedKeys, SharedSecret, SphinxSuite},
16    surb::{ReplyOpener, SURB},
17};
18
19/// Holds data that are padded up to `P + 1`.
20///
21/// Data in this instance is guaranteed to be always `P + 1` bytes-long.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct PaddedPayload<const P: usize>(Box<[u8]>);
24
25impl<const P: usize> PaddedPayload<P> {
26    /// Byte used to pad the data.
27    pub const PADDING: u8 = 0x00;
28    /// Tag used to separate padding from data
29    pub const PADDING_TAG: u8 = 0xaa;
30    /// Size of the padded data.
31    pub const SIZE: usize = P + size_of_val(&Self::PADDING_TAG);
32
33    /// Creates a new instance from the given message `msg` shorter than [`PaddedPayload::SIZE`] and pads it.
34    ///
35    /// The padding consists of prepending a [`PaddedPayload::PADDING_TAG`], preceded by as many zero bytes
36    /// to fill it up to [`PaddedPayload::SIZE`]. If data is `P` bytes-long, only the padding tag is prepended.
37    ///
38    /// If the argument's length is greater or equal to [`PaddedPayload::SIZE`], [`SphinxError::PaddingError`] is
39    /// returned.
40    pub fn new(msg: &[u8]) -> Result<Self, SphinxError> {
41        if msg.len() < Self::SIZE {
42            // Zeroes followed by the PADDING_TAG and then the message
43            let mut ret = vec![Self::PADDING; Self::SIZE];
44            ret[Self::SIZE - msg.len() - 1] = Self::PADDING_TAG;
45            ret[Self::SIZE - msg.len()..].copy_from_slice(msg);
46
47            Ok(Self(ret.into_boxed_slice()))
48        } else {
49            Err(SphinxError::PaddingError)
50        }
51    }
52
53    /// Similar like [`PaddedPayload::new`], but creates a new instance from a vector,
54    /// reallocating only if the given vector has insufficient capacity.
55    pub fn new_from_vec(mut msg: Vec<u8>) -> Result<Self, SphinxError> {
56        let len = msg.len();
57        if len >= Self::SIZE {
58            return Err(SphinxError::PaddingError);
59        }
60
61        msg.resize(Self::SIZE, Self::PADDING); // Reallocates only if capacity is not enough
62        msg.copy_within(0..len, Self::SIZE - len);
63        msg[0..Self::SIZE - len].fill(Self::PADDING);
64        msg[Self::SIZE - len - 1] = Self::PADDING_TAG;
65
66        Ok(Self(msg.into_boxed_slice()))
67    }
68
69    /// Creates a new instance from an already padded message `msg` and takes its ownership.
70    ///
71    /// This method only checks the length of the argument, it does not verify
72    /// the presence of the padding tag. If the padding tag is not present, an error
73    /// is later returned when [`PaddedPayload::into_unpadded`] is called.
74    ///
75    /// If the vector has any excess capacity, it will be trimmed.
76    ///
77    /// If the argument's length is not equal to [`PaddedPayload::SIZE`], [`SphinxError::PaddingError`] is returned.
78    pub fn from_padded(msg: Vec<u8>) -> Result<Self, SphinxError> {
79        if msg.len() == Self::SIZE {
80            Ok(Self(msg.into_boxed_slice()))
81        } else {
82            Err(SphinxError::PaddingError)
83        }
84    }
85
86    /// Consumes the instance by removing the padding and taking ownership of
87    /// the unpadded data. The original length of the data is restored.
88    ///
89    /// If the padding tag could not be found, [`SphinxError::PaddingError`] is returned.
90    /// This means this instance was created using [`PaddedPayload::from_padded`] with invalid data.
91    pub fn into_unpadded(self) -> Result<Box<[u8]>, SphinxError> {
92        self.0
93            .iter()
94            .position(|x| *x == Self::PADDING_TAG)
95            .map(|tag_pos| {
96                let mut data = self.0.into_vec();
97                data.drain(0..=tag_pos);
98                data.into_boxed_slice()
99            })
100            .ok_or(SphinxError::PaddingError)
101    }
102}
103
104impl<const P: usize> AsRef<[u8]> for PaddedPayload<P> {
105    fn as_ref(&self) -> &[u8] {
106        self.0.as_ref()
107    }
108}
109
110impl<const P: usize> Deref for PaddedPayload<P> {
111    type Target = [u8];
112
113    fn deref(&self) -> &Self::Target {
114        self.0.deref()
115    }
116}
117
118impl<const P: usize> DerefMut for PaddedPayload<P> {
119    fn deref_mut(&mut self) -> &mut Self::Target {
120        self.0.deref_mut()
121    }
122}
123
124/// Trait that defines 1:1 mapper between key identifiers and the actual public keys.
125///
126/// This is used to uniquely map between short public key identifiers used in the Sphinx header,
127/// and actual routing addresses (public keys) of the nodes.
128pub trait KeyIdMapper<S: SphinxSuite, H: SphinxHeaderSpec> {
129    /// Maps public key to its unique identifier.
130    fn map_key_to_id(&self, key: &<S::P as Keypair>::Public) -> Option<H::KeyId>;
131    /// Maps public key identifier to the actual public key.
132    fn map_id_to_public(&self, id: &H::KeyId) -> Option<<S::P as Keypair>::Public>;
133    /// Convenience method to map a slice of public keys to IDs.
134    fn map_keys_to_ids(&self, keys: &[<S::P as Keypair>::Public]) -> Vec<Option<H::KeyId>> {
135        keys.iter().map(|key| self.map_key_to_id(key)).collect()
136    }
137    /// Convenience method to map a slice of IDs to public keys.
138    fn map_ids_to_keys(&self, ids: &[H::KeyId]) -> Vec<Option<<S::P as Keypair>::Public>> {
139        ids.iter().map(|id| self.map_id_to_public(id)).collect()
140    }
141}
142
143impl<S, H> KeyIdMapper<S, H> for bimap::BiHashMap<H::KeyId, <S::P as Keypair>::Public>
144where
145    S: SphinxSuite,
146    H: SphinxHeaderSpec,
147    <S::P as Keypair>::Public: Eq + std::hash::Hash,
148    H::KeyId: Eq + std::hash::Hash,
149{
150    fn map_key_to_id(&self, key: &<S::P as Keypair>::Public) -> Option<H::KeyId> {
151        self.get_by_right(key).cloned()
152    }
153
154    fn map_id_to_public(&self, id: &H::KeyId) -> Option<<S::P as Keypair>::Public> {
155        self.get_by_left(id).cloned()
156    }
157}
158
159/// Describes how a [`MetaPacket`] should be routed to the destination.
160pub enum MetaPacketRouting<'a, S: SphinxSuite, H: SphinxHeaderSpec> {
161    /// Uses an explicitly given path to deliver the packet.
162    ForwardPath {
163        /// Shared keys with individual hops
164        shared_keys: SharedKeys<S::E, S::G>,
165        /// Public keys on the path corresponding to the shared keys
166        forward_path: &'a [<S::P as Keypair>::Public],
167        /// Additional data for individual relayers
168        additional_data_relayer: &'a [H::RelayerData],
169        /// Additional data delivered to the packet's final recipient.
170        receiver_data: &'a H::PacketReceiverData,
171        /// Special flag used for acknowledgement signaling to the recipient
172        no_ack: bool,
173    },
174    /// Uses a SURB to deliver the packet and some additional data to the SURB's creator.
175    Surb(SURB<S, H>, &'a H::PacketReceiverData),
176}
177
178/// Represents a packet that is only partially instantiated,
179/// that is - it contains only the routing information and the Alpha value.
180///
181/// This object can be used to pre-compute a packet without a payload
182/// and possibly serialize it, and later to be
183/// deserialized and used to construct the final [`MetaPacket`] via
184/// a call to [`PartialPacket::into_meta_packet`].
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct PartialPacket<S: SphinxSuite, H: SphinxHeaderSpec> {
187    alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
188    routing_info: RoutingInfo<H>,
189    prp_inits: Vec<IvKey<S::PRP>>,
190}
191
192impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialPacket<S, H> {
193    /// Creates a new partial packet using the given routing information and
194    /// public key identifier mapper.
195    pub fn new<M: KeyIdMapper<S, H>>(routing: MetaPacketRouting<S, H>, key_mapper: &M) -> Result<Self, SphinxError> {
196        match routing {
197            MetaPacketRouting::ForwardPath {
198                shared_keys,
199                forward_path,
200                additional_data_relayer,
201                receiver_data,
202                no_ack,
203            } => {
204                let routing_info = RoutingInfo::<H>::new(
205                    &forward_path
206                        .iter()
207                        .map(|key| {
208                            key_mapper.map_key_to_id(key).ok_or_else(|| {
209                                SphinxError::PacketConstructionError(format!("key id not found for {}", key.to_hex()))
210                            })
211                        })
212                        .collect::<Result<Vec<_>, SphinxError>>()?,
213                    &shared_keys.secrets,
214                    additional_data_relayer,
215                    receiver_data,
216                    false,
217                    no_ack,
218                )?;
219
220                Ok(Self {
221                    alpha: shared_keys.alpha,
222                    routing_info,
223                    prp_inits: shared_keys
224                        .secrets
225                        .into_iter()
226                        .rev()
227                        .map(|key| S::new_prp_init(&key))
228                        .collect::<Result<Vec<_>, _>>()?,
229                })
230            }
231            MetaPacketRouting::Surb(surb, receiver_data) => Ok(Self {
232                alpha: surb.alpha,
233                routing_info: surb.header,
234                prp_inits: vec![S::new_reply_prp_init(&surb.sender_key, receiver_data.as_ref())?],
235            }),
236        }
237    }
238
239    /// Transform this partial packet into an actual [`MetaPacket`] using the given payload.
240    pub fn into_meta_packet<const P: usize>(self, mut payload: PaddedPayload<P>) -> MetaPacket<S, H, P> {
241        for iv_key in self.prp_inits {
242            let mut prp = iv_key.into_init::<S::PRP>();
243            prp.apply_keystream(&mut payload);
244        }
245
246        MetaPacket::new_from_parts(self.alpha, self.routing_info, &payload)
247    }
248}
249
250impl<S: SphinxSuite, H: SphinxHeaderSpec> Debug for PartialPacket<S, H> {
251    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
252        f.debug_struct("PartialPacket")
253            .field("alpha", &self.alpha)
254            .field("routing_info", &self.routing_info)
255            .field("prp_inits", &self.prp_inits)
256            .finish()
257    }
258}
259
260impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for PartialPacket<S, H> {
261    fn clone(&self) -> Self {
262        Self {
263            alpha: self.alpha.clone(),
264            routing_info: self.routing_info.clone(),
265            prp_inits: self.prp_inits.clone(),
266        }
267    }
268}
269
270impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for PartialPacket<S, H> {
271    fn eq(&self, other: &Self) -> bool {
272        self.alpha == other.alpha && self.routing_info == other.routing_info && self.prp_inits == other.prp_inits
273    }
274}
275
276impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for PartialPacket<S, H> {}
277
278/// An encrypted packet with a payload of size `P`.
279/// The final packet size is given by [`MetaPacket::SIZE`].
280///
281/// A sender can create a new packet via [`MetaPacket::new`] and send it.
282/// Once received by the recipient, it is parsed first by calling [`MetaPacket::try_from`]
283/// and then it can be transformed into [`ForwardedMetaPacket`] by calling
284/// the [`MetaPacket::into_forwarded`] method. The [`ForwardedMetaPacket`] then contains the information
285/// about the next recipient of this packet or the payload for the final destination.
286///
287/// The packet format is directly dependent on the used [`SphinxSuite`].
288pub struct MetaPacket<S, H, const P: usize> {
289    packet: Box<[u8]>,
290    _d: PhantomData<(S, H)>,
291}
292
293impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> Debug for MetaPacket<S, H, P> {
294    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
295        write!(f, "{}", self.to_hex())
296    }
297}
298
299// Needs manual Clone implementation to not impose Clone restriction on `S` and `H`
300impl<S, H, const P: usize> Clone for MetaPacket<S, H, P> {
301    fn clone(&self) -> Self {
302        Self {
303            packet: self.packet.clone(),
304            _d: PhantomData,
305        }
306    }
307}
308
309/// Represent a [`MetaPacket`] with one layer of encryption removed, exposing the details
310/// about the next hop.
311///
312/// There are two possible states - either the packet is intended for the recipient,
313/// and is thus [`ForwardedMetaPacket::Final`], or it is meant to be sent (relayed)
314/// to the next hop - thus it is [`ForwardedMetaPacket::Relayed`].
315#[allow(dead_code)]
316pub enum ForwardedMetaPacket<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> {
317    /// The content is another [`MetaPacket`] meant to be sent to the next hop.
318    Relayed {
319        /// Packet for the next hop.
320        packet: MetaPacket<S, H, P>,
321        /// Public key of the next hop.
322        next_node: <S::P as Keypair>::Public,
323        /// Position in the channel path of this packet.
324        path_pos: u8,
325        /// Additional data for the relayer.
326        ///
327        /// In HOPR protocol, this contains the PoR challenge that will be solved when we receive
328        /// the acknowledgement after we forward the inner packet to the next hop.
329        additional_info: H::RelayerData,
330        /// Shared secret that was used to encrypt the removed layer.
331        derived_secret: SharedSecret,
332        /// Packet checksum.
333        packet_tag: PacketTag,
334    },
335    /// The content is the actual payload for the packet's destination.
336    Final {
337        /// Decrypted payload
338        plain_text: PaddedPayload<P>,
339        /// Data for the packet receiver (containing the sender's pseudonym).
340        receiver_data: H::PacketReceiverData,
341        /// Shared secret that was used to encrypt the removed layer.
342        derived_secret: SharedSecret,
343        /// Packet checksum.
344        packet_tag: PacketTag,
345        /// Special flag used for acknowledgement signaling to the recipient
346        no_ack: bool,
347    },
348}
349
350impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> MetaPacket<S, H, P> {
351    /// The fixed length of the padded packet.
352    pub const PACKET_LEN: usize = <S::P as Keypair>::Public::SIZE + RoutingInfo::<H>::SIZE + PaddedPayload::<P>::SIZE;
353
354    /// Creates a new outgoing packet with the given payload `msg` and `routing`.
355    ///
356    /// The size of the `msg` must be less or equal `P`, otherwise the
357    /// constructor will return an error.
358    pub fn new<M: KeyIdMapper<S, H>>(
359        payload: PaddedPayload<P>,
360        routing: MetaPacketRouting<S, H>,
361        key_mapper: &M,
362    ) -> Result<Self, SphinxError> {
363        Ok(PartialPacket::new(routing, key_mapper)?.into_meta_packet(payload))
364    }
365
366    fn new_from_parts(
367        alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
368        routing_info: RoutingInfo<H>,
369        payload: &[u8],
370    ) -> Self {
371        let mut packet = Vec::with_capacity(Self::SIZE);
372        packet.extend_from_slice(&alpha);
373        packet.extend_from_slice(routing_info.as_ref());
374        packet.extend_from_slice(&payload[0..PaddedPayload::<P>::SIZE]);
375
376        Self {
377            packet: packet.into_boxed_slice(),
378            _d: PhantomData,
379        }
380    }
381
382    /// Returns the Alpha value subslice from the packet data.
383    fn alpha(&self) -> &[u8] {
384        let len = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
385        &self.packet[..len]
386    }
387
388    /// Returns the routing information from the packet data as a mutable slice.
389    fn routing_info_mut(&mut self) -> &mut [u8] {
390        let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
391        &mut self.packet[base..base + RoutingInfo::<H>::SIZE]
392    }
393
394    /// Returns the payload subslice from the packet data.
395    fn payload_mut(&mut self) -> &mut [u8] {
396        let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE + RoutingInfo::<H>::SIZE;
397        &mut self.packet[base..base + PaddedPayload::<P>::SIZE]
398    }
399
400    /// Attempts to remove the layer of encryption in this packet by using the given `node_keypair`.
401    /// This will transform this packet into the [`ForwardedMetaPacket`].
402    pub fn into_forwarded<K, F>(
403        mut self,
404        node_keypair: &S::P,
405        key_mapper: &K,
406        mut reply_openers: F,
407    ) -> Result<ForwardedMetaPacket<S, H, P>, SphinxError>
408    where
409        K: KeyIdMapper<S, H>,
410        F: FnMut(&H::PacketReceiverData) -> Option<ReplyOpener>,
411    {
412        let (alpha, secret) = SharedKeys::<S::E, S::G>::forward_transform(
413            Alpha::<<S::G as GroupElement<S::E>>::AlphaLen>::from_slice(self.alpha()),
414            &(node_keypair.into()),
415            &(node_keypair.public().into()),
416        )?;
417
418        // Forward the packet header
419        let fwd_header = forward_header::<H>(&secret, self.routing_info_mut())?;
420
421        // Perform initial decryption over the payload
422        let decrypted = self.payload_mut();
423        let mut prp = S::new_prp_init(&secret)?.into_init::<S::PRP>();
424        prp.apply_keystream(decrypted);
425
426        Ok(match fwd_header {
427            ForwardedHeader::Relayed {
428                next_header,
429                path_pos,
430                next_node,
431                additional_info,
432            } => ForwardedMetaPacket::Relayed {
433                packet: Self::new_from_parts(alpha, next_header, decrypted),
434                packet_tag: derive_packet_tag(&secret)?,
435                derived_secret: secret,
436                next_node: key_mapper.map_id_to_public(&next_node).ok_or_else(|| {
437                    SphinxError::PacketDecodingError(format!("couldn't map id to public key: {}", next_node.to_hex()))
438                })?,
439                path_pos,
440                additional_info,
441            },
442            ForwardedHeader::Final {
443                receiver_data,
444                is_reply,
445                no_ack,
446            } => {
447                // If the received packet contains a reply message for a pseudonym,
448                // we must perform additional steps to decrypt it
449                if is_reply {
450                    let local_surb = reply_openers(&receiver_data).ok_or_else(|| {
451                        SphinxError::PacketDecodingError(format!(
452                            "couldn't find reply opener for pseudonym: {}",
453                            receiver_data.to_hex()
454                        ))
455                    })?;
456
457                    // Encrypt the packet payload using the derived shared secrets
458                    // to reverse the decryption done by individual hops
459                    for secret in local_surb.shared_secrets.into_iter().rev() {
460                        let mut prp = S::new_prp_init(&secret)?.into_init::<S::PRP>();
461                        prp.apply_keystream(decrypted);
462                    }
463
464                    // Invert the initial encryption using the sender key
465                    let mut prp =
466                        S::new_reply_prp_init(&local_surb.sender_key, receiver_data.as_ref())?.into_init::<S::PRP>();
467                    prp.apply_keystream(decrypted);
468                }
469
470                // Remove all the data before the actual decrypted payload
471                // and shrink the original allocation.
472                let mut payload = self.packet.into_vec();
473                payload.drain(..<S::G as GroupElement<S::E>>::AlphaLen::USIZE + RoutingInfo::<H>::SIZE);
474
475                ForwardedMetaPacket::Final {
476                    packet_tag: derive_packet_tag(&secret)?,
477                    derived_secret: secret,
478                    plain_text: PaddedPayload::from_padded(payload)?,
479                    receiver_data,
480                    no_ack,
481                }
482            }
483        })
484    }
485}
486
487impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> AsRef<[u8]> for MetaPacket<S, H, P> {
488    fn as_ref(&self) -> &[u8] {
489        self.packet.as_ref()
490    }
491}
492
493impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<&[u8]> for MetaPacket<S, H, P> {
494    type Error = GeneralError;
495
496    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
497        if value.len() == Self::SIZE {
498            Ok(Self {
499                packet: value.into(),
500                _d: PhantomData,
501            })
502        } else {
503            Err(GeneralError::ParseError("MetaPacket".into()))
504        }
505    }
506}
507
508impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> BytesRepresentable for MetaPacket<S, H, P> {
509    const SIZE: usize =
510        <S::G as GroupElement<S::E>>::AlphaLen::USIZE + RoutingInfo::<H>::SIZE + PaddedPayload::<P>::SIZE;
511}
512
513#[cfg(test)]
514pub(crate) mod tests {
515    use std::{hash::Hash, num::NonZeroUsize};
516
517    use anyhow::anyhow;
518    use bimap::BiHashMap;
519    use hopr_crypto_random::Randomizable;
520    use hopr_crypto_types::keypairs::{Keypair, OffchainKeypair};
521    use parameterized::parameterized;
522
523    use super::*;
524    use crate::{surb::create_surb, tests::WrappedBytes};
525
526    #[derive(Debug, Clone, Copy)]
527    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
528    struct TestHeader<S: SphinxSuite>(PhantomData<S>);
529
530    impl<S: SphinxSuite> SphinxHeaderSpec for TestHeader<S> {
531        type KeyId = KeyIdent<4>;
532        type PRG = hopr_crypto_types::primitives::ChaCha20;
533        type PacketReceiverData = SimplePseudonym;
534        type Pseudonym = SimplePseudonym;
535        type RelayerData = WrappedBytes<53>;
536        type SurbReceiverData = WrappedBytes<54>;
537        type UH = hopr_crypto_types::primitives::Poly1305;
538
539        const MAX_HOPS: NonZeroUsize = NonZeroUsize::new(4).unwrap();
540    }
541
542    const PAYLOAD_SIZE: usize = 799;
543
544    #[test]
545    fn test_padding() -> anyhow::Result<()> {
546        let data = b"some testing forward message";
547        let padded = PaddedPayload::<PAYLOAD_SIZE>::new(data)?;
548
549        let mut expected = vec![0u8; PAYLOAD_SIZE - data.len()];
550        expected.push(PaddedPayload::<PAYLOAD_SIZE>::PADDING_TAG);
551        expected.extend_from_slice(data);
552        assert_eq!(expected.len(), padded.len());
553        assert_eq!(&expected, padded.as_ref());
554
555        let padded_from_vec = PaddedPayload::<PAYLOAD_SIZE>::new_from_vec(data.to_vec())?;
556        assert_eq!(padded, padded_from_vec);
557
558        let unpadded = padded.into_unpadded()?;
559        assert!(!unpadded.is_empty());
560        assert_eq!(data, unpadded.as_ref());
561
562        Ok(())
563    }
564
565    #[test]
566    fn test_padding_zero_length() -> anyhow::Result<()> {
567        let data = [];
568        let padded = PaddedPayload::<9>::new(&data)?;
569        assert_eq!(padded.len(), 10);
570        assert_eq!(padded.as_ref()[9], PaddedPayload::<9>::PADDING_TAG);
571        assert_eq!(&padded.as_ref()[0..9], &[0u8; 9]);
572
573        Ok(())
574    }
575
576    #[test]
577    fn test_padding_full_length() -> anyhow::Result<()> {
578        let data = [1u8; 9];
579        let padded = PaddedPayload::<9>::new(&data)?;
580        assert_eq!(padded.len(), 10);
581        assert_eq!(padded.as_ref()[0], PaddedPayload::<9>::PADDING_TAG);
582        assert_eq!(padded.as_ref()[1..], data);
583
584        Ok(())
585    }
586
587    #[cfg(feature = "serde")]
588    fn generic_test_partial_packet_serialization<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
589    where
590        S: SphinxSuite + PartialEq,
591        <S::P as Keypair>::Public: Eq + Hash,
592    {
593        let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
594        let mapper = keypairs
595            .iter()
596            .enumerate()
597            .map(|(i, k)| (KeyIdent::from(i as u32), k.public().clone()))
598            .collect::<BiHashMap<_, _>>();
599
600        let shared_keys = S::new_shared_keys(&pubkeys)?;
601        let por_strings = vec![WrappedBytes::<53>::default(); shared_keys.secrets.len() - 1];
602        let pseudonym = SimplePseudonym::random();
603
604        let packet_1 = PartialPacket::<S, TestHeader<S>>::new(
605            MetaPacketRouting::ForwardPath {
606                shared_keys,
607                forward_path: &pubkeys,
608                additional_data_relayer: &por_strings,
609                receiver_data: &pseudonym,
610                no_ack: false,
611            },
612            &mapper,
613        )?;
614
615        const BINCODE_CONFIGURATION: bincode::config::Configuration = bincode::config::standard()
616            .with_little_endian()
617            .with_variable_int_encoding();
618
619        let encoded_1 = bincode::serde::encode_to_vec(&packet_1, BINCODE_CONFIGURATION)?;
620        let packet_2: PartialPacket<S, TestHeader<S>> =
621            bincode::serde::decode_from_slice(&encoded_1, BINCODE_CONFIGURATION)?.0;
622
623        assert_eq!(packet_1, packet_2);
624        Ok(())
625    }
626
627    fn generic_test_meta_packet<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
628    where
629        S: SphinxSuite,
630        <S::P as Keypair>::Public: Eq + Hash,
631    {
632        let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
633        let mapper = keypairs
634            .iter()
635            .enumerate()
636            .map(|(i, k)| (KeyIdent::from(i as u32), k.public().clone()))
637            .collect::<BiHashMap<_, _>>();
638
639        let shared_keys = S::new_shared_keys(&pubkeys)?;
640        let por_strings = vec![WrappedBytes::<53>::default(); shared_keys.secrets.len() - 1];
641        let pseudonym = SimplePseudonym::random();
642
643        assert_eq!(shared_keys.secrets.len() - 1, por_strings.len());
644
645        let msg = b"some random test message";
646
647        let mut mp = MetaPacket::<S, TestHeader<S>, PAYLOAD_SIZE>::new(
648            PaddedPayload::new(msg)?,
649            MetaPacketRouting::ForwardPath {
650                shared_keys,
651                forward_path: &pubkeys,
652                additional_data_relayer: &por_strings,
653                receiver_data: &pseudonym,
654                no_ack: false,
655            },
656            &mapper,
657        )?;
658
659        assert!(mp.as_ref().len() < 1492, "metapacket too long {}", mp.as_ref().len());
660
661        let mut received_plaintext = Box::default();
662        for (i, pair) in keypairs.iter().enumerate() {
663            let fwd = mp
664                .clone()
665                .into_forwarded(pair, &mapper, |_| None)
666                .unwrap_or_else(|_| panic!("failed to unwrap at {i}"));
667
668            match fwd {
669                ForwardedMetaPacket::Relayed { packet, .. } => {
670                    assert!(i < keypairs.len() - 1);
671                    mp = packet;
672                }
673                ForwardedMetaPacket::Final { plain_text, .. } => {
674                    assert_eq!(keypairs.len() - 1, i);
675                    received_plaintext = plain_text.into_unpadded()?;
676                }
677            }
678        }
679
680        assert_eq!(msg, received_plaintext.as_ref());
681
682        Ok(())
683    }
684
685    fn generic_meta_packet_reply_test<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
686    where
687        S: SphinxSuite,
688        <S::P as Keypair>::Public: Eq + Hash,
689    {
690        let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
691        let mapper = keypairs
692            .iter()
693            .enumerate()
694            .map(|(i, k)| (KeyIdent::from(i as u32), k.public().clone()))
695            .collect::<BiHashMap<_, _>>();
696
697        let shared_keys = S::new_shared_keys(&pubkeys)?;
698        let por_strings = vec![WrappedBytes::default(); shared_keys.secrets.len() - 1];
699        let por_values = WrappedBytes::default();
700        let pseudonym = SimplePseudonym::random();
701
702        let ids = <BiHashMap<_, _> as KeyIdMapper<S, TestHeader<S>>>::map_keys_to_ids(&mapper, &pubkeys)
703            .into_iter()
704            .map(|v| v.ok_or_else(|| anyhow!("failed to map keys to ids")))
705            .collect::<anyhow::Result<Vec<KeyIdent>>>()?;
706
707        let (surb, opener) = create_surb::<S, TestHeader<S>>(shared_keys, &ids, &por_strings, pseudonym, por_values)?;
708
709        let msg = b"some random reply test message";
710
711        let mut mp = MetaPacket::<S, TestHeader<S>, PAYLOAD_SIZE>::new(
712            PaddedPayload::new(msg)?,
713            MetaPacketRouting::Surb(surb, &pseudonym),
714            &mapper,
715        )?;
716
717        let surb_retriever = |p: &SimplePseudonym| {
718            assert_eq!(pseudonym, *p);
719            Some(opener.clone())
720        };
721
722        let mut received_plaintext = Box::default();
723        for (i, pair) in keypairs.iter().enumerate() {
724            let fwd = mp
725                .clone()
726                .into_forwarded(pair, &mapper, surb_retriever)
727                .unwrap_or_else(|_| panic!("failed to unwrap at {i}"));
728
729            match fwd {
730                ForwardedMetaPacket::Relayed { packet, .. } => {
731                    assert!(i < keypairs.len() - 1);
732                    mp = packet;
733                }
734                ForwardedMetaPacket::Final { plain_text, .. } => {
735                    assert_eq!(keypairs.len() - 1, i);
736                    received_plaintext = plain_text.into_unpadded()?;
737                }
738            }
739        }
740
741        assert_eq!(msg, received_plaintext.as_ref());
742
743        Ok(())
744    }
745
746    #[cfg(feature = "x25519")]
747    #[parameterized(amount = { 4, 3, 2, 1 })]
748    fn test_x25519_meta_packet(amount: usize) -> anyhow::Result<()> {
749        generic_test_meta_packet::<crate::ec_groups::X25519Suite>(
750            (0..amount).map(|_| OffchainKeypair::random()).collect(),
751        )
752    }
753
754    #[cfg(feature = "x25519")]
755    #[parameterized(amount = { 4, 3, 2, 1 })]
756    fn test_x25519_reply_meta_packet(amount: usize) -> anyhow::Result<()> {
757        generic_meta_packet_reply_test::<crate::ec_groups::X25519Suite>(
758            (0..amount).map(|_| OffchainKeypair::random()).collect(),
759        )
760    }
761
762    #[cfg(all(feature = "x25519", feature = "serde"))]
763    #[parameterized(amount = { 4, 3, 2, 1 })]
764    fn test_x25519_partial_packet_serialize(amount: usize) -> anyhow::Result<()> {
765        generic_test_partial_packet_serialization::<crate::ec_groups::X25519Suite>(
766            (0..amount).map(|_| OffchainKeypair::random()).collect(),
767        )
768    }
769
770    #[cfg(feature = "ed25519")]
771    #[parameterized(amount = { 4, 3, 2, 1 })]
772    fn test_ed25519_meta_packet(amount: usize) -> anyhow::Result<()> {
773        generic_test_meta_packet::<crate::ec_groups::Ed25519Suite>(
774            (0..amount).map(|_| OffchainKeypair::random()).collect(),
775        )
776    }
777
778    #[cfg(feature = "ed25519")]
779    #[parameterized(amount = { 4, 3, 2, 1 })]
780    fn test_ed25519_reply_meta_packet(amount: usize) -> anyhow::Result<()> {
781        generic_meta_packet_reply_test::<crate::ec_groups::Ed25519Suite>(
782            (0..amount).map(|_| OffchainKeypair::random()).collect(),
783        )
784    }
785
786    #[cfg(all(feature = "ed25519", feature = "serde"))]
787    #[parameterized(amount = { 4, 3, 2, 1 })]
788    fn test_ed25519_partial_packet_serialize(amount: usize) -> anyhow::Result<()> {
789        generic_test_partial_packet_serialization::<crate::ec_groups::Ed25519Suite>(
790            (0..amount).map(|_| OffchainKeypair::random()).collect(),
791        )
792    }
793
794    #[cfg(feature = "secp256k1")]
795    #[parameterized(amount = { 4, 3, 2, 1 })]
796    fn test_secp256k1_meta_packet(amount: usize) -> anyhow::Result<()> {
797        generic_test_meta_packet::<crate::ec_groups::Secp256k1Suite>(
798            (0..amount)
799                .map(|_| hopr_crypto_types::keypairs::ChainKeypair::random())
800                .collect(),
801        )
802    }
803
804    #[cfg(feature = "secp256k1")]
805    #[parameterized(amount = { 4, 3, 2, 1 })]
806    fn test_secp256k1_reply_meta_packet(amount: usize) -> anyhow::Result<()> {
807        generic_meta_packet_reply_test::<crate::ec_groups::Secp256k1Suite>(
808            (0..amount)
809                .map(|_| hopr_crypto_types::keypairs::ChainKeypair::random())
810                .collect(),
811        )
812    }
813
814    #[cfg(all(feature = "secp256k1", feature = "serde"))]
815    #[parameterized(amount = { 4, 3, 2, 1 })]
816    fn test_secp256k1_partial_packet_serialize(amount: usize) -> anyhow::Result<()> {
817        generic_test_partial_packet_serialization::<crate::ec_groups::Secp256k1Suite>(
818            (0..amount)
819                .map(|_| hopr_crypto_types::keypairs::ChainKeypair::random())
820                .collect(),
821        )
822    }
823}