hopr_crypto_packet/
types.rs

1use std::{borrow::Cow, fmt::Formatter, marker::PhantomData, ops::Not};
2
3use hopr_crypto_sphinx::{
4    errors::SphinxError,
5    prelude::{PaddedPayload, SURB, SphinxHeaderSpec, SphinxSuite},
6};
7use hopr_crypto_types::prelude::HashFast;
8use hopr_internal_types::prelude::HoprPseudonym;
9use hopr_primitive_types::prelude::{BytesRepresentable, GeneralError};
10
11use crate::{HoprSphinxHeaderSpec, HoprSphinxSuite, PAYLOAD_SIZE_INT};
12
13flagset::flags! {
14   /// Individual packet signals passed up between the packet sender and destination.
15   #[repr(u8)]
16   #[derive(PartialOrd, Ord, strum::EnumString, strum::Display)]
17   pub enum PacketSignal: u8 {
18        /// The other party is in a "SURB distress" state, potentially running out of SURBs soon.
19        ///
20        /// Has no effect on packets that take the "forward path".
21        SurbDistress = 0b0000_0001,
22        /// The other party has run out of SURBs, and this was potentially the last message they could
23        /// send.
24        ///
25        /// Has no effect on packets that take the "forward path".
26        ///
27        /// Implies [`SurbDistress`].
28        OutOfSurbs = 0b0000_0011,
29   }
30}
31
32/// Packet signal states that can be passed between the packet sender and destination.
33///
34/// These signals can be typically propagated up to the application layer to take an appropriate
35/// action to the signaled states.
36pub type PacketSignals = flagset::FlagSet<PacketSignal>;
37
38/// Size of the [`HoprSurbId`] in bytes.
39pub const SURB_ID_SIZE: usize = 8;
40
41/// An ID that uniquely identifies SURB for a certain pseudonym.
42pub type HoprSurbId = [u8; SURB_ID_SIZE];
43
44/// Identifier of a single packet sender.
45///
46/// This consists of two parts:
47/// - [`HoprSenderId::pseudonym`] of the sender
48/// - [`HoprSenderId::surb_id`] is an identifier a single SURB that routes the packet back to the sender.
49///
50/// The `surb_id` always identifies a single SURB. The instance can be turned into a pseudorandom
51/// sequence using [`HoprSenderId::into_sequence`] to create identifiers for more SURBs
52/// with the same pseudonym.
53#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct HoprSenderId(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
56
57impl HoprSenderId {
58    pub fn new(pseudonym: &HoprPseudonym) -> Self {
59        let mut ret: [u8; Self::SIZE] = hopr_crypto_random::random_bytes();
60        ret[..HoprPseudonym::SIZE].copy_from_slice(pseudonym.as_ref());
61        Self(ret)
62    }
63
64    pub fn from_pseudonym_and_id(pseudonym: &HoprPseudonym, id: HoprSurbId) -> Self {
65        let mut ret = [0u8; Self::SIZE];
66        ret[..HoprPseudonym::SIZE].copy_from_slice(pseudonym.as_ref());
67        ret[HoprPseudonym::SIZE..HoprPseudonym::SIZE + SURB_ID_SIZE].copy_from_slice(&id);
68        Self(ret)
69    }
70
71    pub fn pseudonym(&self) -> HoprPseudonym {
72        HoprPseudonym::try_from(&self.0[..HoprPseudonym::SIZE]).expect("must have valid pseudonym")
73    }
74
75    pub fn surb_id(&self) -> HoprSurbId {
76        self.0[HoprPseudonym::SIZE..HoprPseudonym::SIZE + SURB_ID_SIZE]
77            .try_into()
78            .expect("must have valid nonce")
79    }
80
81    /// Creates a pseudorandom sequence of IDs.
82    ///
83    /// Each item has the same [`pseudonym`](HoprSenderId::pseudonym)
84    /// but different [`surb_id`](HoprSenderId::surb_id).
85    ///
86    /// The `surb_id` of the `n`-th item (n > 1) is computed as `Blake3(n || I_prev)`
87    /// where `I_prev` is the whole `n-1`-th ID, the `n` is represented as big-endian and
88    /// `||` denotes byte-array concatenation.
89    /// The first item (n = 1) is always `self`.
90    ///
91    /// The entropy of the whole pseudorandom sequence is completely given by `self` (the first
92    /// item in the sequence). It follows that the next element of the sequence can be computed
93    /// by just knowing any preceding element; therefore, the sequence is fully predictable
94    /// once an element is known.
95    pub fn into_sequence(self) -> impl Iterator<Item = Self> {
96        std::iter::successors(Some((1u32, self)), |&(i, prev)| {
97            let hash = HashFast::create(&[&i.to_be_bytes(), prev.as_ref()]);
98            Some((
99                i + 1,
100                Self::from_pseudonym_and_id(&prev.pseudonym(), hash.as_ref()[0..SURB_ID_SIZE].try_into().unwrap()),
101            ))
102        })
103        .map(|(_, v)| v)
104    }
105}
106
107impl AsRef<[u8]> for HoprSenderId {
108    fn as_ref(&self) -> &[u8] {
109        &self.0
110    }
111}
112
113impl<'a> TryFrom<&'a [u8]> for HoprSenderId {
114    type Error = GeneralError;
115
116    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
117        value
118            .try_into()
119            .map(Self)
120            .map_err(|_| GeneralError::ParseError("HoprPacketReceiverData.size".into()))
121    }
122}
123
124impl BytesRepresentable for HoprSenderId {
125    const SIZE: usize = HoprPseudonym::SIZE + SURB_ID_SIZE;
126}
127
128impl hopr_crypto_random::Randomizable for HoprSenderId {
129    fn random() -> Self {
130        Self::new(&HoprPseudonym::random())
131    }
132}
133
134/// Additional encoding of a packet message that can be preceded by a number of [`SURBs`](SURB).
135pub struct PacketMessage<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize>(PaddedPayload<P>, PhantomData<(S, H)>);
136
137/// Convenience alias for HOPR specific [`PacketMessage`].
138pub type HoprPacketMessage = PacketMessage<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>;
139
140/// Individual parts of a [`PacketMessage`]: SURBs, the actual message (payload) and additional signals for the
141/// recipient.
142pub struct PacketParts<'a, S: SphinxSuite, H: SphinxHeaderSpec> {
143    /// Contains (a potentially empty) list of SURBs.
144    pub surbs: Vec<SURB<S, H>>,
145    /// Contains the actual packet payload.
146    pub payload: Cow<'a, [u8]>,
147    /// Additional packet signals from the sender to the recipient.
148    pub signals: PacketSignals,
149}
150
151impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for PacketParts<'_, S, H>
152where
153    H::KeyId: Clone,
154    H::SurbReceiverData: Clone,
155{
156    fn clone(&self) -> Self {
157        Self {
158            surbs: self.surbs.clone(),
159            payload: self.payload.clone(),
160            signals: self.signals,
161        }
162    }
163}
164
165impl<S: SphinxSuite, H: SphinxHeaderSpec> std::fmt::Debug for PacketParts<'_, S, H>
166where
167    H::KeyId: std::fmt::Debug,
168    H::SurbReceiverData: std::fmt::Debug,
169{
170    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
171        f.debug_struct("PacketParts")
172            .field("surbs", &self.surbs)
173            .field("payload", &self.payload)
174            .field("signals", &self.signals)
175            .finish()
176    }
177}
178
179impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for PacketParts<'_, S, H>
180where
181    H::KeyId: PartialEq,
182    H::SurbReceiverData: PartialEq,
183{
184    fn eq(&self, other: &Self) -> bool {
185        self.surbs == other.surbs && self.payload == other.payload && self.signals == other.signals
186    }
187}
188
189impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for PacketParts<'_, S, H>
190where
191    H::KeyId: Eq,
192    H::SurbReceiverData: Eq,
193{
194}
195
196/// Convenience alias for HOPR specific [`PacketParts`].
197pub type HoprPacketParts<'a> = PacketParts<'a, HoprSphinxSuite, HoprSphinxHeaderSpec>;
198
199// Coerces PacketSignals to only lower 4 bits.
200pub(crate) const S_MASK: u8 = 0b0000_1111;
201
202impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> PacketMessage<S, H, P> {
203    /// Size of the message header.
204    ///
205    /// This is currently 1 byte to indicate the number of SURBs that precede the message.
206    pub const HEADER_LEN: usize = 1;
207    /// The maximum number of SURBs a packet message can hold, according to RFC-0003.
208    ///
209    /// The number of SURBs in a `PacketMessage` is intentionally limited to 15, so that
210    /// the upper 4 bits remain reserved for additional flags.
211    pub const MAX_SURBS_PER_MESSAGE: usize = S_MASK as usize;
212}
213
214impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<PacketParts<'_, S, H>> for PacketMessage<S, H, P> {
215    type Error = SphinxError;
216
217    fn try_from(value: PacketParts<S, H>) -> Result<Self, Self::Error> {
218        if value.surbs.len() > Self::MAX_SURBS_PER_MESSAGE {
219            return Err(GeneralError::ParseError("HoprPacketMessage.num_surbs not valid".into()).into());
220        }
221
222        if value.signals.bits() > S_MASK {
223            return Err(GeneralError::ParseError("HoprPacketMessage.flags not valid".into()).into());
224        }
225
226        // The total size of the packet message must not exceed the maximum packet size.
227        if Self::HEADER_LEN + value.surbs.len() * SURB::<S, H>::SIZE + value.payload.len() > P {
228            return Err(GeneralError::ParseError("HoprPacketMessage.size not valid".into()).into());
229        }
230
231        let mut ret = Vec::with_capacity(PaddedPayload::<P>::SIZE);
232        let flags_and_len = (value.signals.bits() << S_MASK.trailing_ones()) | (value.surbs.len() as u8 & S_MASK);
233        ret.push(flags_and_len);
234        for surb in value.surbs.into_iter().map(|s| s.into_boxed()) {
235            ret.extend(surb);
236        }
237        ret.extend_from_slice(value.payload.as_ref());
238
239        // Save one reallocation by using the vector that we just created
240        Ok(Self(PaddedPayload::new_from_vec(ret)?, PhantomData))
241    }
242}
243
244impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<PacketMessage<S, H, P>> for PacketParts<'_, S, H> {
245    type Error = SphinxError;
246
247    fn try_from(value: PacketMessage<S, H, P>) -> Result<Self, Self::Error> {
248        let data = value.0.into_unpadded()?;
249        if data.is_empty() {
250            return Err(GeneralError::ParseError("HoprPacketMessage.size".into()).into());
251        }
252
253        let num_surbs = (data[0] & S_MASK) as usize;
254        let signals = PacketSignals::new((data[0] & S_MASK.not()) >> S_MASK.trailing_ones())
255            .map_err(|_| GeneralError::ParseError("HoprPacketMessage.signals".into()))?;
256
257        if num_surbs > 0 {
258            let surb_end = num_surbs * SURB::<S, H>::SIZE;
259            if surb_end >= data.len() {
260                return Err(GeneralError::ParseError("HoprPacketMessage.num_surbs not valid".into()).into());
261            }
262
263            let mut data = data.into_vec();
264
265            let surbs = data[1..=surb_end]
266                .chunks_exact(SURB::<S, H>::SIZE)
267                .map(SURB::<S, H>::try_from)
268                .collect::<Result<Vec<_>, _>>()?;
269
270            // Skip buffer all the way to the end of the SURBs.
271            data.drain(0..=surb_end).for_each(drop);
272
273            Ok(PacketParts {
274                surbs,
275                payload: Cow::Owned(data),
276                signals,
277            })
278        } else {
279            let mut data = data.into_vec();
280            data.remove(0);
281            Ok(PacketParts {
282                surbs: Vec::with_capacity(0),
283                payload: Cow::Owned(data),
284                signals,
285            })
286        }
287    }
288}
289
290impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> From<PaddedPayload<P>> for PacketMessage<S, H, P> {
291    fn from(value: PaddedPayload<P>) -> Self {
292        Self(value, PhantomData)
293    }
294}
295
296impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> From<PacketMessage<S, H, P>> for PaddedPayload<P> {
297    fn from(value: PacketMessage<S, H, P>) -> Self {
298        value.0
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use anyhow::anyhow;
305    use bimap::BiHashMap;
306    use hex_literal::hex;
307    use hopr_crypto_random::Randomizable;
308    use hopr_crypto_sphinx::prelude::*;
309    use hopr_crypto_types::prelude::*;
310    use hopr_primitive_types::prelude::*;
311
312    use super::*;
313    use crate::{
314        HoprSphinxHeaderSpec, HoprSphinxSuite, HoprSurb,
315        packet::HoprPacket,
316        por::{SurbReceiverInfo, generate_proof_of_relay},
317    };
318
319    lazy_static::lazy_static! {
320        static ref PEERS: [(ChainKeypair, OffchainKeypair); 4] = [
321            (hex!("a7c486ceccf5ab53bd428888ab1543dc2667abd2d5e80aae918da8d4b503a426"), hex!("5eb212d4d6aa5948c4f71574d45dad43afef6d330edb873fca69d0e1b197e906")),
322            (hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"), hex!("e995db483ada5174666c46bafbf3628005aca449c94ebdc0c9239c3f65d61ae0")),
323            (hex!("ca4bdfd54a8467b5283a0216288fdca7091122479ccf3cfb147dfa59d13f3486"), hex!("9dec751c00f49e50fceff7114823f726a0425a68a8dc6af0e4287badfea8f4a4")),
324            (hex!("e306ebfb0d01d0da0952c9a567d758093a80622c6cb55052bf5f1a6ebd8d7b5c"), hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"))
325        ].map(|(p1,p2)| (ChainKeypair::from_secret(&p1).expect("lazy static keypair should be valid"), OffchainKeypair::from_secret(&p2).expect("lazy static keypair should be valid")));
326
327        static ref MAPPER: bimap::BiMap<KeyIdent, OffchainPublicKey> = PEERS
328            .iter()
329            .enumerate()
330            .map(|(i, (_, k))| (KeyIdent::from(i as u32), *k.public()))
331            .collect::<BiHashMap<_, _>>();
332    }
333
334    fn generate_surbs(count: usize) -> anyhow::Result<Vec<SURB<HoprSphinxSuite, HoprSphinxHeaderSpec>>> {
335        let path = PEERS.iter().map(|(_, k)| *k.public()).collect::<Vec<_>>();
336        let path_ids =
337            <BiHashMap<_, _> as KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>>::map_keys_to_ids(&*MAPPER, &path)
338                .into_iter()
339                .map(|v| v.ok_or(anyhow!("missing id")))
340                .collect::<Result<Vec<_>, _>>()?;
341        let pseudonym = SimplePseudonym::random();
342        let recv_data = HoprSenderId::new(&pseudonym);
343
344        Ok((0..count)
345            .map(|_| {
346                let shared_keys = HoprSphinxSuite::new_shared_keys(&path)?;
347                let (por_strings, por_values) = generate_proof_of_relay(&shared_keys.secrets)
348                    .map_err(|e| CryptoError::Other(GeneralError::NonSpecificError(e.to_string())))?;
349
350                create_surb::<HoprSphinxSuite, HoprSphinxHeaderSpec>(
351                    shared_keys,
352                    &path_ids,
353                    &por_strings,
354                    recv_data,
355                    SurbReceiverInfo::new(por_values, [0u8; 32]),
356                )
357                .map(|(s, _)| s)
358            })
359            .collect::<Result<Vec<_>, _>>()?)
360    }
361
362    #[test]
363    fn hopr_packet_message_message_only() -> anyhow::Result<()> {
364        let parts_1 = HoprPacketParts {
365            surbs: vec![],
366            payload: b"test message".into(),
367            signals: PacketSignal::OutOfSurbs.into(),
368        };
369
370        let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
371        assert_eq!(parts_1, parts_2);
372
373        Ok(())
374    }
375
376    #[test]
377    fn hopr_packet_message_surbs_only() -> anyhow::Result<()> {
378        let parts_1 = HoprPacketParts {
379            surbs: generate_surbs(2)?,
380            payload: Cow::default(),
381            signals: PacketSignal::OutOfSurbs.into(),
382        };
383
384        let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
385        assert_eq!(parts_1, parts_2);
386
387        Ok(())
388    }
389
390    #[test]
391    fn hopr_packet_message_surbs_and_msg() -> anyhow::Result<()> {
392        let parts_1 = HoprPacketParts {
393            surbs: generate_surbs(2)?,
394            payload: b"test msg".into(),
395            signals: PacketSignal::OutOfSurbs.into(),
396        };
397
398        let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
399        assert_eq!(parts_1, parts_2);
400
401        Ok(())
402    }
403
404    #[test]
405    fn hopr_packet_size_msg_size_limit() {
406        let res = HoprPacketMessage::try_from(HoprPacketParts {
407            surbs: vec![],
408            payload: (&[1u8; HoprPacket::PAYLOAD_SIZE + 1]).into(),
409            signals: None.into(),
410        });
411        assert!(res.is_err());
412    }
413
414    #[test]
415    fn hopr_packet_message_surbs_size_limit() -> anyhow::Result<()> {
416        let res = HoprPacketMessage::try_from(PacketParts {
417            surbs: generate_surbs(HoprPacketMessage::MAX_SURBS_PER_MESSAGE + 1)?,
418            payload: Cow::default(),
419            signals: None.into(),
420        });
421        assert!(res.is_err());
422
423        let res = HoprPacketMessage::try_from(HoprPacketParts {
424            surbs: generate_surbs(3)?,
425            payload: Cow::default(),
426            signals: None.into(),
427        });
428        assert!(res.is_err());
429
430        Ok(())
431    }
432
433    #[test]
434    fn hopr_packet_message_surbs_flag_limit() -> anyhow::Result<()> {
435        let res = HoprPacketMessage::try_from(PacketParts {
436            surbs: generate_surbs(3)?,
437            payload: Cow::default(),
438            signals: unsafe { PacketSignals::new_unchecked(16) },
439        });
440        assert!(res.is_err());
441
442        Ok(())
443    }
444
445    #[test]
446    fn hopr_packet_size_msg_and_surb_size_limit() -> anyhow::Result<()> {
447        let res = HoprPacketMessage::try_from(PacketParts {
448            surbs: generate_surbs(2)?,
449            payload: (&[1u8; HoprPacket::PAYLOAD_SIZE - 2 * HoprSurb::SIZE + 1]).into(),
450            signals: None.into(),
451        });
452        assert!(res.is_err());
453
454        Ok(())
455    }
456}