Skip to main content

hopr_crypto_packet/sphinx/
packet.rs

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