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