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    pub fn into_meta_packet<const P: usize>(self, mut payload: PaddedPayload<P>) -> MetaPacket<S, H, P> {
242        for iv_key in self.prp_inits {
243            let prp = iv_key.into_init::<S::PRP>();
244            // The following won't panic, because PaddedPayload<P> is guaranteed to be S::PRP::BlockSize bytes-long
245            // However, it would be nicer to make PaddedPayload take P as a typenum parameter
246            // and enforce this invariant at compile time.
247            let block = crypto_traits::Block::<S::PRP>::from_mut_slice(&mut payload);
248            prp.forward(block);
249        }
250
251        MetaPacket::new_from_parts(self.alpha, self.routing_info, &payload)
252    }
253}
254
255impl<S: SphinxSuite, H: SphinxHeaderSpec> Debug for PartialPacket<S, H> {
256    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
257        f.debug_struct("PartialPacket")
258            .field("alpha", &self.alpha)
259            .field("routing_info", &self.routing_info)
260            .field("prp_inits", &self.prp_inits)
261            .finish()
262    }
263}
264
265impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for PartialPacket<S, H> {
266    fn clone(&self) -> Self {
267        Self {
268            alpha: self.alpha.clone(),
269            routing_info: self.routing_info.clone(),
270            prp_inits: self.prp_inits.clone(),
271        }
272    }
273}
274
275impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for PartialPacket<S, H> {
276    fn eq(&self, other: &Self) -> bool {
277        self.alpha == other.alpha && self.routing_info == other.routing_info && self.prp_inits == other.prp_inits
278    }
279}
280
281impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for PartialPacket<S, H> {}
282
283/// An encrypted packet with a payload of size `P`.
284/// The final packet size is given by [`MetaPacket::SIZE`].
285///
286/// A sender can create a new packet via [`MetaPacket::new`] and send it.
287/// Once received by the recipient, it is parsed first by calling [`MetaPacket::try_from`]
288/// and then it can be transformed into [`ForwardedMetaPacket`] by calling
289/// the [`MetaPacket::into_forwarded`] method. The [`ForwardedMetaPacket`] then contains the information
290/// about the next recipient of this packet or the payload for the final destination.
291///
292/// The packet format is directly dependent on the used [`SphinxSuite`].
293pub struct MetaPacket<S, H, const P: usize> {
294    packet: Box<[u8]>,
295    _d: PhantomData<(S, H)>,
296}
297
298impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> Debug for MetaPacket<S, H, P> {
299    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
300        write!(f, "{}", self.to_hex())
301    }
302}
303
304// Needs manual Clone implementation to not impose Clone restriction on `S` and `H`
305impl<S, H, const P: usize> Clone for MetaPacket<S, H, P> {
306    fn clone(&self) -> Self {
307        Self {
308            packet: self.packet.clone(),
309            _d: PhantomData,
310        }
311    }
312}
313
314/// Represent a [`MetaPacket`] with one layer of encryption removed, exposing the details
315/// about the next hop.
316///
317/// There are two possible states - either the packet is intended for the recipient,
318/// and is thus [`ForwardedMetaPacket::Final`], or it is meant to be sent (relayed)
319/// to the next hop - thus it is [`ForwardedMetaPacket::Relayed`].
320#[allow(dead_code)]
321pub enum ForwardedMetaPacket<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> {
322    /// The content is another [`MetaPacket`] meant to be sent to the next hop.
323    Relayed {
324        /// Packet for the next hop.
325        packet: MetaPacket<S, H, P>,
326        /// Public key of the next hop.
327        next_node: <S::P as Keypair>::Public,
328        /// Position in the channel path of this packet.
329        path_pos: u8,
330        /// Additional data for the relayer.
331        ///
332        /// In HOPR protocol, this contains the PoR challenge that will be solved when we receive
333        /// the acknowledgement after we forward the inner packet to the next hop.
334        additional_info: H::RelayerData,
335        /// Shared secret that was used to encrypt the removed layer.
336        derived_secret: SharedSecret,
337        /// Packet checksum.
338        packet_tag: PacketTag,
339    },
340    /// The content is the actual payload for the packet's destination.
341    Final {
342        /// Decrypted payload
343        plain_text: PaddedPayload<P>,
344        /// Data for the packet receiver (containing the sender's pseudonym).
345        receiver_data: H::PacketReceiverData,
346        /// Shared secret that was used to encrypt the removed layer.
347        derived_secret: SharedSecret,
348        /// Packet checksum.
349        packet_tag: PacketTag,
350        /// Special flag used for acknowledgement signaling to the recipient
351        no_ack: bool,
352    },
353}
354
355impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> MetaPacket<S, H, P> {
356    /// The fixed length of the padded packet.
357    pub const PACKET_LEN: usize = <S::P as Keypair>::Public::SIZE + RoutingInfo::<H>::SIZE + PaddedPayload::<P>::SIZE;
358
359    /// Creates a new outgoing packet with the given payload `msg` and `routing`.
360    ///
361    /// The size of the `msg` must be less or equal `P`, otherwise the
362    /// constructor will return an error.
363    pub fn new<M: KeyIdMapper<S, H>>(
364        payload: PaddedPayload<P>,
365        routing: MetaPacketRouting<S, H>,
366        key_mapper: &M,
367    ) -> Result<Self, SphinxError> {
368        Ok(PartialPacket::new(routing, key_mapper)?.into_meta_packet(payload))
369    }
370
371    fn new_from_parts(
372        alpha: Alpha<<S::G as GroupElement<S::E>>::AlphaLen>,
373        routing_info: RoutingInfo<H>,
374        payload: &[u8],
375    ) -> Self {
376        let mut packet = Vec::with_capacity(Self::SIZE);
377        packet.extend_from_slice(&alpha);
378        packet.extend_from_slice(routing_info.as_ref());
379        packet.extend_from_slice(&payload[0..PaddedPayload::<P>::SIZE]);
380
381        Self {
382            packet: packet.into_boxed_slice(),
383            _d: PhantomData,
384        }
385    }
386
387    /// Returns the Alpha value subslice from the packet data.
388    fn alpha(&self) -> &[u8] {
389        let len = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
390        &self.packet[..len]
391    }
392
393    /// Returns the routing information from the packet data as a mutable slice.
394    fn routing_info_mut(&mut self) -> &mut [u8] {
395        let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE;
396        &mut self.packet[base..base + RoutingInfo::<H>::SIZE]
397    }
398
399    /// Returns the payload subslice from the packet data.
400    ///
401    /// This data is guaranteed to be `PaddedPayload::<P>::SIZE` bytes-long, which currently
402    /// is `P + 1` bytes.
403    fn payload_mut(&mut self) -> &mut [u8] {
404        let base = <S::G as GroupElement<S::E>>::AlphaLen::USIZE + RoutingInfo::<H>::SIZE;
405        &mut self.packet[base..base + PaddedPayload::<P>::SIZE]
406    }
407
408    /// Attempts to remove the layer of encryption in this packet by using the given `node_keypair`.
409    /// This will transform this packet into the [`ForwardedMetaPacket`].
410    pub fn into_forwarded<'a, K, F>(
411        mut self,
412        node_keypair: &'a S::P,
413        key_mapper: &K,
414        mut reply_openers: F,
415    ) -> Result<ForwardedMetaPacket<S, H, P>, SphinxError>
416    where
417        K: KeyIdMapper<S, H>,
418        F: FnMut(&H::PacketReceiverData) -> Option<ReplyOpener>,
419        &'a Alpha<<S::G as GroupElement<S::E>>::AlphaLen>: From<&'a <S::P as Keypair>::Public>,
420    {
421        let (alpha, secret) = SharedKeys::<S::E, S::G>::forward_transform(
422            Alpha::<<S::G as GroupElement<S::E>>::AlphaLen>::from_slice(self.alpha()),
423            &(node_keypair.into()),
424            node_keypair.public().into(),
425        )?;
426
427        // Forward the packet header
428        let fwd_header = forward_header::<H>(&secret, self.routing_info_mut())?;
429
430        // Perform initial decryption over the payload
431        let decrypted = self.payload_mut();
432        let prp = S::new_prp_init(&secret)?.into_init::<S::PRP>();
433        prp.inverse(decrypted.into());
434
435        Ok(match fwd_header {
436            ForwardedHeader::Relayed {
437                next_header,
438                path_pos,
439                next_node,
440                additional_info,
441            } => ForwardedMetaPacket::Relayed {
442                packet: Self::new_from_parts(alpha, next_header, decrypted),
443                packet_tag: derive_packet_tag(&secret)?,
444                derived_secret: secret,
445                next_node: key_mapper.map_id_to_public(&next_node).ok_or_else(|| {
446                    SphinxError::PacketDecodingError(format!("couldn't map id to public key: {}", next_node.to_hex()))
447                })?,
448                path_pos,
449                additional_info,
450            },
451            ForwardedHeader::Final {
452                receiver_data,
453                is_reply,
454                no_ack,
455            } => {
456                // If the received packet contains a reply message for a pseudonym,
457                // we must perform additional steps to decrypt it
458                if is_reply {
459                    let local_surb = reply_openers(&receiver_data).ok_or_else(|| {
460                        SphinxError::PacketDecodingError(format!(
461                            "couldn't find reply opener for pseudonym: {}",
462                            receiver_data.to_hex()
463                        ))
464                    })?;
465
466                    // Encrypt the packet payload using the derived shared secrets
467                    // to reverse the decryption done by individual hops
468                    for secret in local_surb.shared_secrets.into_iter().rev() {
469                        let prp = S::new_prp_init(&secret)?.into_init::<S::PRP>();
470                        prp.forward(decrypted.into());
471                    }
472
473                    // Invert the initial encryption using the sender key
474                    let prp =
475                        S::new_reply_prp_init(&local_surb.sender_key, receiver_data.as_ref())?.into_init::<S::PRP>();
476                    prp.inverse(decrypted.into());
477                }
478
479                // Remove all the data before the actual decrypted payload
480                // and shrink the original allocation.
481                let mut payload = self.packet.into_vec();
482                payload.drain(..<S::G as GroupElement<S::E>>::AlphaLen::USIZE + RoutingInfo::<H>::SIZE);
483
484                ForwardedMetaPacket::Final {
485                    packet_tag: derive_packet_tag(&secret)?,
486                    derived_secret: secret,
487                    plain_text: PaddedPayload::from_padded(payload)?,
488                    receiver_data,
489                    no_ack,
490                }
491            }
492        })
493    }
494}
495
496impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> AsRef<[u8]> for MetaPacket<S, H, P> {
497    fn as_ref(&self) -> &[u8] {
498        self.packet.as_ref()
499    }
500}
501
502impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<&[u8]> for MetaPacket<S, H, P> {
503    type Error = GeneralError;
504
505    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
506        if value.len() == Self::SIZE {
507            Ok(Self {
508                packet: value.into(),
509                _d: PhantomData,
510            })
511        } else {
512            Err(GeneralError::ParseError("MetaPacket".into()))
513        }
514    }
515}
516
517impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> BytesRepresentable for MetaPacket<S, H, P> {
518    const SIZE: usize =
519        <S::G as GroupElement<S::E>>::AlphaLen::USIZE + RoutingInfo::<H>::SIZE + PaddedPayload::<P>::SIZE;
520}
521
522#[cfg(test)]
523pub(crate) mod tests {
524    use std::{hash::Hash, num::NonZeroUsize};
525
526    use anyhow::anyhow;
527    use bimap::BiHashMap;
528    use hopr_crypto_random::Randomizable;
529    use hopr_crypto_types::keypairs::{Keypair, OffchainKeypair};
530    use parameterized::parameterized;
531
532    use super::*;
533    use crate::{surb::create_surb, tests::WrappedBytes};
534
535    #[derive(Debug, Clone, Copy)]
536    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
537    struct TestHeader<S: SphinxSuite>(PhantomData<S>);
538
539    impl<S: SphinxSuite> SphinxHeaderSpec for TestHeader<S> {
540        type KeyId = KeyIdent<4>;
541        type PRG = hopr_crypto_types::primitives::ChaCha20;
542        type PacketReceiverData = SimplePseudonym;
543        type Pseudonym = SimplePseudonym;
544        type RelayerData = WrappedBytes<53>;
545        type SurbReceiverData = WrappedBytes<54>;
546        type UH = hopr_crypto_types::primitives::Poly1305;
547
548        const MAX_HOPS: NonZeroUsize = NonZeroUsize::new(4).unwrap();
549    }
550
551    const PAYLOAD_SIZE: usize = 1021;
552
553    #[test]
554    fn test_padding() -> anyhow::Result<()> {
555        let data = b"some testing forward message";
556        let padded = PaddedPayload::<PAYLOAD_SIZE>::new(data)?;
557
558        let mut expected = vec![0u8; PAYLOAD_SIZE - data.len()];
559        expected.push(PaddedPayload::<PAYLOAD_SIZE>::PADDING_TAG);
560        expected.extend_from_slice(data);
561        assert_eq!(expected.len(), padded.len());
562        assert_eq!(&expected, padded.as_ref());
563
564        let padded_from_vec = PaddedPayload::<PAYLOAD_SIZE>::new_from_vec(data.to_vec())?;
565        assert_eq!(padded, padded_from_vec);
566
567        let unpadded = padded.into_unpadded()?;
568        assert!(!unpadded.is_empty());
569        assert_eq!(data, unpadded.as_ref());
570
571        Ok(())
572    }
573
574    #[test]
575    fn test_padding_zero_length() -> anyhow::Result<()> {
576        let data = [];
577        let padded = PaddedPayload::<9>::new(&data)?;
578        assert_eq!(padded.len(), 10);
579        assert_eq!(padded.as_ref()[9], PaddedPayload::<9>::PADDING_TAG);
580        assert_eq!(&padded.as_ref()[0..9], &[0u8; 9]);
581
582        Ok(())
583    }
584
585    #[test]
586    fn test_padding_full_length() -> anyhow::Result<()> {
587        let data = [1u8; 9];
588        let padded = PaddedPayload::<9>::new(&data)?;
589        assert_eq!(padded.len(), 10);
590        assert_eq!(padded.as_ref()[0], PaddedPayload::<9>::PADDING_TAG);
591        assert_eq!(padded.as_ref()[1..], data);
592
593        Ok(())
594    }
595
596    #[cfg(feature = "serde")]
597    fn generic_test_partial_packet_serialization<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
598    where
599        S: SphinxSuite + PartialEq,
600        <S::P as Keypair>::Public: Eq + Hash,
601        for<'a> &'a Alpha<<<S as SphinxSuite>::G as GroupElement<<S as SphinxSuite>::E>>::AlphaLen>:
602            From<&'a <<S as SphinxSuite>::P as Keypair>::Public>,
603    {
604        let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
605        let mapper = keypairs
606            .iter()
607            .enumerate()
608            .map(|(i, k)| (KeyIdent::from(i as u32), k.public().clone()))
609            .collect::<BiHashMap<_, _>>();
610
611        let shared_keys = S::new_shared_keys(&pubkeys)?;
612        let por_strings = vec![WrappedBytes::<53>::default(); shared_keys.secrets.len() - 1];
613        let pseudonym = SimplePseudonym::random();
614
615        let packet_1 = PartialPacket::<S, TestHeader<S>>::new(
616            MetaPacketRouting::ForwardPath {
617                shared_keys,
618                forward_path: &pubkeys,
619                additional_data_relayer: &por_strings,
620                receiver_data: &pseudonym,
621                no_ack: false,
622            },
623            &mapper,
624        )?;
625
626        const BINCODE_CONFIGURATION: bincode::config::Configuration = bincode::config::standard()
627            .with_little_endian()
628            .with_variable_int_encoding();
629
630        let encoded_1 = bincode::serde::encode_to_vec(&packet_1, BINCODE_CONFIGURATION)?;
631        let packet_2: PartialPacket<S, TestHeader<S>> =
632            bincode::serde::decode_from_slice(&encoded_1, BINCODE_CONFIGURATION)?.0;
633
634        assert_eq!(packet_1, packet_2);
635        Ok(())
636    }
637
638    fn generic_test_meta_packet<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
639    where
640        S: SphinxSuite,
641        <S::P as Keypair>::Public: Eq + Hash,
642        for<'a> &'a Alpha<<S::G as GroupElement<S::E>>::AlphaLen>: From<&'a <S::P as Keypair>::Public>,
643    {
644        let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
645        let mapper = keypairs
646            .iter()
647            .enumerate()
648            .map(|(i, k)| (KeyIdent::from(i as u32), k.public().clone()))
649            .collect::<BiHashMap<_, _>>();
650
651        let shared_keys = S::new_shared_keys(&pubkeys)?;
652        let por_strings = vec![WrappedBytes::<53>::default(); shared_keys.secrets.len() - 1];
653        let pseudonym = SimplePseudonym::random();
654
655        assert_eq!(shared_keys.secrets.len() - 1, por_strings.len());
656
657        let msg = b"some random test message";
658
659        let mut mp = MetaPacket::<S, TestHeader<S>, PAYLOAD_SIZE>::new(
660            PaddedPayload::new(msg)?,
661            MetaPacketRouting::ForwardPath {
662                shared_keys,
663                forward_path: &pubkeys,
664                additional_data_relayer: &por_strings,
665                receiver_data: &pseudonym,
666                no_ack: false,
667            },
668            &mapper,
669        )?;
670
671        assert!(mp.as_ref().len() < 1492, "metapacket too long {}", mp.as_ref().len());
672
673        let mut received_plaintext = Box::default();
674        for (i, pair) in keypairs.iter().enumerate() {
675            let fwd = mp
676                .clone()
677                .into_forwarded(pair, &mapper, |_| None)
678                .unwrap_or_else(|_| panic!("failed to unwrap at {i}"));
679
680            match fwd {
681                ForwardedMetaPacket::Relayed { packet, .. } => {
682                    assert!(i < keypairs.len() - 1);
683                    mp = packet;
684                }
685                ForwardedMetaPacket::Final { plain_text, .. } => {
686                    assert_eq!(keypairs.len() - 1, i);
687                    received_plaintext = plain_text.into_unpadded()?;
688                }
689            }
690        }
691
692        assert_eq!(msg, received_plaintext.as_ref());
693
694        Ok(())
695    }
696
697    fn generic_meta_packet_reply_test<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
698    where
699        S: SphinxSuite,
700        <S::P as Keypair>::Public: Eq + Hash,
701        for<'a> &'a Alpha<<<S as SphinxSuite>::G as GroupElement<<S as SphinxSuite>::E>>::AlphaLen>:
702            From<&'a <<S as SphinxSuite>::P as Keypair>::Public>,
703    {
704        let pubkeys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
705        let mapper = keypairs
706            .iter()
707            .enumerate()
708            .map(|(i, k)| (KeyIdent::from(i as u32), k.public().clone()))
709            .collect::<BiHashMap<_, _>>();
710
711        let shared_keys = S::new_shared_keys(&pubkeys)?;
712        let por_strings = vec![WrappedBytes::default(); shared_keys.secrets.len() - 1];
713        let por_values = WrappedBytes::default();
714        let pseudonym = SimplePseudonym::random();
715
716        let ids = <BiHashMap<_, _> as KeyIdMapper<S, TestHeader<S>>>::map_keys_to_ids(&mapper, &pubkeys)
717            .into_iter()
718            .map(|v| v.ok_or_else(|| anyhow!("failed to map keys to ids")))
719            .collect::<anyhow::Result<Vec<KeyIdent>>>()?;
720
721        let (surb, opener) = create_surb::<S, TestHeader<S>>(shared_keys, &ids, &por_strings, pseudonym, por_values)?;
722
723        let msg = b"some random reply test message";
724
725        let mut mp = MetaPacket::<S, TestHeader<S>, PAYLOAD_SIZE>::new(
726            PaddedPayload::new(msg)?,
727            MetaPacketRouting::Surb(surb, &pseudonym),
728            &mapper,
729        )?;
730
731        let surb_retriever = |p: &SimplePseudonym| {
732            assert_eq!(pseudonym, *p);
733            Some(opener.clone())
734        };
735
736        let mut received_plaintext = Box::default();
737        for (i, pair) in keypairs.iter().enumerate() {
738            let fwd = mp
739                .clone()
740                .into_forwarded(pair, &mapper, surb_retriever)
741                .unwrap_or_else(|_| panic!("failed to unwrap at {i}"));
742
743            match fwd {
744                ForwardedMetaPacket::Relayed { packet, .. } => {
745                    assert!(i < keypairs.len() - 1);
746                    mp = packet;
747                }
748                ForwardedMetaPacket::Final { plain_text, .. } => {
749                    assert_eq!(keypairs.len() - 1, i);
750                    received_plaintext = plain_text.into_unpadded()?;
751                }
752            }
753        }
754
755        assert_eq!(msg, received_plaintext.as_ref());
756
757        Ok(())
758    }
759
760    #[cfg(feature = "x25519")]
761    #[parameterized(amount = { 4, 3, 2, 1 })]
762    fn test_x25519_meta_packet(amount: usize) -> anyhow::Result<()> {
763        generic_test_meta_packet::<crate::ec_groups::X25519Suite>(
764            (0..amount).map(|_| OffchainKeypair::random()).collect(),
765        )
766    }
767
768    #[cfg(feature = "x25519")]
769    #[parameterized(amount = { 4, 3, 2, 1 })]
770    fn test_x25519_reply_meta_packet(amount: usize) -> anyhow::Result<()> {
771        generic_meta_packet_reply_test::<crate::ec_groups::X25519Suite>(
772            (0..amount).map(|_| OffchainKeypair::random()).collect(),
773        )
774    }
775
776    #[cfg(all(feature = "x25519", feature = "serde"))]
777    #[parameterized(amount = { 4, 3, 2, 1 })]
778    fn test_x25519_partial_packet_serialize(amount: usize) -> anyhow::Result<()> {
779        generic_test_partial_packet_serialization::<crate::ec_groups::X25519Suite>(
780            (0..amount).map(|_| OffchainKeypair::random()).collect(),
781        )
782    }
783
784    #[cfg(feature = "ed25519")]
785    #[parameterized(amount = { 4, 3, 2, 1 })]
786    fn test_ed25519_meta_packet(amount: usize) -> anyhow::Result<()> {
787        generic_test_meta_packet::<crate::ec_groups::Ed25519Suite>(
788            (0..amount).map(|_| OffchainKeypair::random()).collect(),
789        )
790    }
791
792    #[cfg(feature = "ed25519")]
793    #[parameterized(amount = { 4, 3, 2, 1 })]
794    fn test_ed25519_reply_meta_packet(amount: usize) -> anyhow::Result<()> {
795        generic_meta_packet_reply_test::<crate::ec_groups::Ed25519Suite>(
796            (0..amount).map(|_| OffchainKeypair::random()).collect(),
797        )
798    }
799
800    #[cfg(all(feature = "ed25519", feature = "serde"))]
801    #[parameterized(amount = { 4, 3, 2, 1 })]
802    fn test_ed25519_partial_packet_serialize(amount: usize) -> anyhow::Result<()> {
803        generic_test_partial_packet_serialization::<crate::ec_groups::Ed25519Suite>(
804            (0..amount).map(|_| OffchainKeypair::random()).collect(),
805        )
806    }
807
808    #[cfg(feature = "secp256k1")]
809    #[parameterized(amount = { 4, 3, 2, 1 })]
810    fn test_secp256k1_meta_packet(amount: usize) -> anyhow::Result<()> {
811        generic_test_meta_packet::<crate::ec_groups::Secp256k1Suite>(
812            (0..amount)
813                .map(|_| hopr_crypto_types::keypairs::ChainKeypair::random())
814                .collect(),
815        )
816    }
817
818    #[cfg(feature = "secp256k1")]
819    #[parameterized(amount = { 4, 3, 2, 1 })]
820    fn test_secp256k1_reply_meta_packet(amount: usize) -> anyhow::Result<()> {
821        generic_meta_packet_reply_test::<crate::ec_groups::Secp256k1Suite>(
822            (0..amount)
823                .map(|_| hopr_crypto_types::keypairs::ChainKeypair::random())
824                .collect(),
825        )
826    }
827
828    #[cfg(all(feature = "secp256k1", feature = "serde"))]
829    #[parameterized(amount = { 4, 3, 2, 1 })]
830    fn test_secp256k1_partial_packet_serialize(amount: usize) -> anyhow::Result<()> {
831        generic_test_partial_packet_serialization::<crate::ec_groups::Secp256k1Suite>(
832            (0..amount)
833                .map(|_| hopr_crypto_types::keypairs::ChainKeypair::random())
834                .collect(),
835        )
836    }
837}