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 #[repr(u8)]
16 #[derive(PartialOrd, Ord, strum::EnumString, strum::Display)]
17 pub enum PacketSignal: u8 {
18 SurbDistress = 0b0000_0001,
22 OutOfSurbs = 0b0000_0011,
29 }
30}
31
32pub type PacketSignals = flagset::FlagSet<PacketSignal>;
37
38pub const SURB_ID_SIZE: usize = 8;
40
41pub type HoprSurbId = [u8; SURB_ID_SIZE];
43
44#[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 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
134pub struct PacketMessage<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize>(PaddedPayload<P>, PhantomData<(S, H)>);
136
137pub type HoprPacketMessage = PacketMessage<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>;
139
140pub struct PacketParts<'a, S: SphinxSuite, H: SphinxHeaderSpec> {
143 pub surbs: Vec<SURB<S, H>>,
145 pub payload: Cow<'a, [u8]>,
147 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
196pub type HoprPacketParts<'a> = PacketParts<'a, HoprSphinxSuite, HoprSphinxHeaderSpec>;
198
199pub(crate) const S_MASK: u8 = 0b0000_1111;
201
202impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> PacketMessage<S, H, P> {
203 pub const HEADER_LEN: usize = 1;
207 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 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 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 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}