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