1use std::fmt::{Display, Formatter};
2
3use hopr_crypto_sphinx::prelude::*;
4use hopr_crypto_types::prelude::*;
5use hopr_internal_types::prelude::*;
6use hopr_path::{NonEmptyPath, TransportPath};
7use hopr_primitive_types::prelude::*;
8
9use crate::{
10 HoprPseudonym, HoprReplyOpener, HoprSphinxHeaderSpec, HoprSphinxSuite, HoprSurb, PAYLOAD_SIZE_INT,
11 errors::{
12 PacketError::{PacketConstructionError, PacketDecodingError},
13 Result,
14 },
15 por::{SurbReceiverInfo, derive_ack_key_share, generate_proof_of_relay, pre_verify},
16 types::{HoprPacketMessage, HoprSenderId, HoprSurbId},
17};
18
19#[derive(Clone)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct PartialHoprPacket {
30 partial_packet: PartialPacket<HoprSphinxSuite, HoprSphinxHeaderSpec>,
31 surbs: Vec<HoprSurb>,
32 openers: Vec<HoprReplyOpener>,
33 ticket: Ticket,
34 next_hop: OffchainPublicKey,
35 ack_challenge: HalfKeyChallenge,
36}
37
38impl PartialHoprPacket {
39 pub fn new<M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>, P: NonEmptyPath<OffchainPublicKey>>(
50 pseudonym: &HoprPseudonym,
51 routing: PacketRouting<P>,
52 chain_keypair: &ChainKeypair,
53 ticket: TicketBuilder,
54 mapper: &M,
55 domain_separator: &Hash,
56 ) -> Result<Self> {
57 match routing {
58 PacketRouting::ForwardPath {
59 forward_path,
60 return_paths,
61 } => {
62 let shared_keys = HoprSphinxSuite::new_shared_keys(&forward_path)?;
64 let (por_strings, por_values) = generate_proof_of_relay(&shared_keys.secrets)?;
65 let receiver_data = HoprSenderId::new(pseudonym);
66
67 let (surbs, openers): (Vec<_>, Vec<_>) = return_paths
69 .iter()
70 .zip(receiver_data.into_sequence())
71 .map(|(rp, data)| create_surb_for_path(rp, data, mapper))
72 .collect::<Result<Vec<_>>>()?
73 .into_iter()
74 .unzip();
75
76 let ticket = ticket
78 .challenge(por_values.ticket_challenge())
79 .build_signed(chain_keypair, domain_separator)?
80 .leak();
81
82 Ok(Self {
83 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
84 MetaPacketRouting::ForwardPath {
85 shared_keys,
86 forward_path: &forward_path,
87 receiver_data: &receiver_data,
88 additional_data_relayer: &por_strings,
89 no_ack: false,
90 },
91 mapper,
92 )?,
93 surbs,
94 openers,
95 ticket,
96 next_hop: forward_path[0],
97 ack_challenge: por_values.acknowledgement_challenge(),
98 })
99 }
100 PacketRouting::Surb(id, surb) => {
101 let ticket = ticket
103 .challenge(surb.additional_data_receiver.proof_of_relay_values().ticket_challenge())
104 .build_signed(chain_keypair, domain_separator)?
105 .leak();
106
107 Ok(Self {
108 ticket,
109 next_hop: mapper.map_id_to_public(&surb.first_relayer).ok_or_else(|| {
110 PacketConstructionError(format!(
111 "failed to map key id {} to public key",
112 surb.first_relayer.to_hex()
113 ))
114 })?,
115 ack_challenge: surb
116 .additional_data_receiver
117 .proof_of_relay_values()
118 .acknowledgement_challenge(),
119 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
120 MetaPacketRouting::Surb(surb, &HoprSenderId::from_pseudonym_and_id(pseudonym, id)),
121 mapper,
122 )?,
123 surbs: vec![],
124 openers: vec![],
125 })
126 }
127 PacketRouting::NoAck(destination) => {
128 let shared_keys = HoprSphinxSuite::new_shared_keys(&[destination])?;
130 let (por_strings, por_values) = generate_proof_of_relay(&shared_keys.secrets)?;
131
132 let ticket = ticket
134 .challenge(por_values.ticket_challenge())
135 .build_signed(chain_keypair, domain_separator)?
136 .leak();
137
138 Ok(Self {
139 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
140 MetaPacketRouting::ForwardPath {
141 shared_keys,
142 forward_path: &[destination],
143 receiver_data: &HoprSenderId::new(pseudonym),
144 additional_data_relayer: &por_strings,
145 no_ack: true, },
147 mapper,
148 )?,
149 ticket,
150 next_hop: destination,
151 ack_challenge: por_values.acknowledgement_challenge(),
152 surbs: vec![],
153 openers: vec![],
154 })
155 }
156 }
157 }
158
159 pub fn into_hopr_packet(self, msg: &[u8]) -> Result<(HoprPacket, Vec<HoprReplyOpener>)> {
162 let msg = HoprPacketMessage::from_parts(self.surbs, msg)?;
163 Ok((
164 HoprPacket::Outgoing(
165 HoprOutgoingPacket {
166 packet: self.partial_packet.into_meta_packet(msg.into()),
167 ticket: self.ticket,
168 next_hop: self.next_hop,
169 ack_challenge: self.ack_challenge,
170 }
171 .into(),
172 ),
173 self.openers,
174 ))
175 }
176}
177
178#[derive(Clone)]
180pub struct HoprIncomingPacket {
181 pub packet_tag: PacketTag,
183 pub ack_key: Option<HalfKey>,
188 pub previous_hop: OffchainPublicKey,
190 pub plain_text: Box<[u8]>,
192 pub sender: HoprPseudonym,
194 pub surbs: Vec<(HoprSurbId, HoprSurb)>,
196}
197
198#[derive(Clone)]
200pub struct HoprOutgoingPacket {
201 pub packet: MetaPacket<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>,
203 pub ticket: Ticket,
205 pub next_hop: OffchainPublicKey,
207 pub ack_challenge: HalfKeyChallenge,
209}
210
211#[derive(Clone)]
213pub struct HoprForwardedPacket {
214 pub outgoing: HoprOutgoingPacket,
216 pub packet_tag: PacketTag,
218 pub ack_key: HalfKey,
220 pub previous_hop: OffchainPublicKey,
222 pub own_key: HalfKey,
224 pub next_challenge: EthereumChallenge,
226 pub path_pos: u8,
228}
229
230#[derive(Clone, strum::EnumTryAs, strum::EnumIs)]
236pub enum HoprPacket {
237 Final(Box<HoprIncomingPacket>),
239 Forwarded(Box<HoprForwardedPacket>),
241 Outgoing(Box<HoprOutgoingPacket>),
243}
244
245impl Display for HoprPacket {
246 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
247 match &self {
248 Self::Final(_) => write!(f, "Final"),
249 Self::Forwarded(_) => write!(f, "Forwarded"),
250 Self::Outgoing(_) => write!(f, "Outgoing"),
251 }
252 }
253}
254
255#[derive(Clone)]
257pub enum PacketRouting<P: NonEmptyPath<OffchainPublicKey> = TransportPath> {
258 ForwardPath { forward_path: P, return_paths: Vec<P> },
262 Surb(HoprSurbId, HoprSurb),
264 NoAck(OffchainPublicKey),
267}
268
269fn create_surb_for_path<M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>, P: NonEmptyPath<OffchainPublicKey>>(
270 return_path: &P,
271 recv_data: HoprSenderId,
272 mapper: &M,
273) -> Result<(HoprSurb, HoprReplyOpener)> {
274 let shared_keys = HoprSphinxSuite::new_shared_keys(return_path)?;
275 let (por_strings, por_values) = generate_proof_of_relay(&shared_keys.secrets)?;
276
277 Ok(create_surb::<HoprSphinxSuite, HoprSphinxHeaderSpec>(
278 shared_keys,
279 &return_path
280 .iter()
281 .map(|k| {
282 mapper
283 .map_key_to_id(k)
284 .ok_or_else(|| PacketConstructionError(format!("failed to map key {} to id", k.to_hex())))
285 })
286 .collect::<Result<Vec<_>>>()?,
287 &por_strings,
288 recv_data,
289 SurbReceiverInfo::new(por_values, [0u8; 32]),
290 )
291 .map(|(s, r)| (s, (recv_data.surb_id(), r)))?)
292}
293
294impl HoprPacket {
295 pub const MAX_SURBS_IN_PACKET: usize = HoprPacket::PAYLOAD_SIZE / HoprSurb::SIZE;
297 pub const PAYLOAD_SIZE: usize = PAYLOAD_SIZE_INT - HoprPacketMessage::HEADER_LEN;
301 pub const SIZE: usize =
303 MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN + Ticket::SIZE;
304
305 pub fn into_outgoing<M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>, P: NonEmptyPath<OffchainPublicKey>>(
319 msg: &[u8],
320 pseudonym: &HoprPseudonym,
321 routing: PacketRouting<P>,
322 chain_keypair: &ChainKeypair,
323 ticket: TicketBuilder,
324 mapper: &M,
325 domain_separator: &Hash,
326 ) -> Result<(Self, Vec<HoprReplyOpener>)> {
327 PartialHoprPacket::new(pseudonym, routing, chain_keypair, ticket, mapper, domain_separator)?
328 .into_hopr_packet(msg)
329 }
330
331 pub const fn max_surbs_with_message(msg_len: usize) -> usize {
334 HoprPacket::PAYLOAD_SIZE.saturating_sub(msg_len) / HoprSurb::SIZE
335 }
336
337 pub const fn max_message_with_surbs(num_surbs: usize) -> usize {
340 HoprPacket::PAYLOAD_SIZE.saturating_sub(num_surbs * HoprSurb::SIZE)
341 }
342
343 pub fn from_incoming<M, F>(
346 data: &[u8],
347 node_keypair: &OffchainKeypair,
348 previous_hop: OffchainPublicKey,
349 mapper: &M,
350 reply_openers: F,
351 ) -> Result<Self>
352 where
353 M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
354 F: FnMut(&HoprSenderId) -> Option<ReplyOpener>,
355 {
356 if data.len() == Self::SIZE {
357 let (pre_packet, pre_ticket) =
358 data.split_at(MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN);
359
360 let mp: MetaPacket<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT> =
361 MetaPacket::try_from(pre_packet)?;
362
363 match mp.into_forwarded(node_keypair, mapper, reply_openers)? {
364 ForwardedMetaPacket::Relayed {
365 packet,
366 derived_secret,
367 additional_info,
368 packet_tag,
369 next_node,
370 path_pos,
371 ..
372 } => {
373 let ack_key = derive_ack_key_share(&derived_secret);
374
375 let ticket = Ticket::try_from(pre_ticket)?;
376 let verification_output = pre_verify(&derived_secret, &additional_info, &ticket.challenge)?;
377 Ok(Self::Forwarded(
378 HoprForwardedPacket {
379 outgoing: HoprOutgoingPacket {
380 packet,
381 ticket,
382 next_hop: next_node,
383 ack_challenge: verification_output.ack_challenge,
384 },
385 packet_tag,
386 ack_key,
387 previous_hop,
388 path_pos,
389 own_key: verification_output.own_key,
390 next_challenge: verification_output.next_ticket_challenge,
391 }
392 .into(),
393 ))
394 }
395 ForwardedMetaPacket::Final {
396 packet_tag,
397 plain_text,
398 derived_secret,
399 receiver_data,
400 no_ack,
401 } => {
402 let (surbs, plain_text) = HoprPacketMessage::from(plain_text).try_into_parts()?;
404 let should_acknowledge = !no_ack;
405 Ok(Self::Final(
406 HoprIncomingPacket {
407 packet_tag,
408 ack_key: (should_acknowledge).then_some(derive_ack_key_share(&derived_secret)),
409 previous_hop,
410 plain_text,
411 surbs: receiver_data.into_sequence().map(|d| d.surb_id()).zip(surbs).collect(),
412 sender: receiver_data.pseudonym(),
413 }
414 .into(),
415 ))
416 }
417 }
418 } else {
419 Err(PacketDecodingError("packet has invalid size".into()))
420 }
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use anyhow::{Context, bail};
427 use bimap::BiHashMap;
428 use hex_literal::hex;
429 use hopr_crypto_random::Randomizable;
430 use hopr_path::TransportPath;
431 use parameterized::parameterized;
432
433 use super::*;
434
435 lazy_static::lazy_static! {
436 static ref PEERS: [(ChainKeypair, OffchainKeypair); 5] = [
437 (hex!("a7c486ceccf5ab53bd428888ab1543dc2667abd2d5e80aae918da8d4b503a426"), hex!("5eb212d4d6aa5948c4f71574d45dad43afef6d330edb873fca69d0e1b197e906")),
438 (hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"), hex!("e995db483ada5174666c46bafbf3628005aca449c94ebdc0c9239c3f65d61ae0")),
439 (hex!("ca4bdfd54a8467b5283a0216288fdca7091122479ccf3cfb147dfa59d13f3486"), hex!("9dec751c00f49e50fceff7114823f726a0425a68a8dc6af0e4287badfea8f4a4")),
440 (hex!("e306ebfb0d01d0da0952c9a567d758093a80622c6cb55052bf5f1a6ebd8d7b5c"), hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed")),
441 (hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775"), hex!("e0bf93e9c916104da00b1850adc4608bd7e9087bbd3f805451f4556aa6b3fd6e")),
442 ].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")));
443
444 static ref MAPPER: bimap::BiMap<KeyIdent, OffchainPublicKey> = PEERS
445 .iter()
446 .enumerate()
447 .map(|(i, (_, k))| (KeyIdent::from(i as u32), *k.public()))
448 .collect::<BiHashMap<_, _>>();
449 }
450
451 fn forward(
452 mut packet: HoprPacket,
453 chain_keypair: &ChainKeypair,
454 next_ticket: TicketBuilder,
455 domain_separator: &Hash,
456 ) -> HoprPacket {
457 if let HoprPacket::Forwarded(fwd) = &mut packet {
458 fwd.outgoing.ticket = next_ticket
459 .challenge(fwd.next_challenge)
460 .build_signed(chain_keypair, domain_separator)
461 .expect("ticket should create")
462 .leak();
463 }
464
465 packet
466 }
467
468 impl HoprPacket {
469 pub fn to_bytes(&self) -> Box<[u8]> {
470 let dummy_ticket = hex!("67f0ca18102feec505e5bfedcc25963e9c64a6f8a250adcad7d2830dd607585700000000000000000000000000000000000000000000000000000000000000003891bf6fd4a78e868fc7ad477c09b16fc70dd01ea67e18264d17e3d04f6d8576de2e6472b0072e510df6e9fa1dfcc2727cc7633edfeb9ec13860d9ead29bee71d68de3736c2f7a9f42de76ccd57a5f5847bc7349");
471 let (packet, ticket) = match self {
472 Self::Final(packet) => (packet.plain_text.clone(), dummy_ticket.as_ref().into()),
473 Self::Forwarded(fwd) => (
474 Vec::from(fwd.outgoing.packet.as_ref()).into_boxed_slice(),
475 fwd.outgoing.ticket.clone().into_boxed(),
476 ),
477 Self::Outgoing(out) => (
478 Vec::from(out.packet.as_ref()).into_boxed_slice(),
479 out.ticket.clone().into_boxed(),
480 ),
481 };
482
483 let mut ret = Vec::with_capacity(Self::SIZE);
484 ret.extend_from_slice(packet.as_ref());
485 ret.extend_from_slice(&ticket);
486 ret.into_boxed_slice()
487 }
488 }
489
490 fn mock_ticket(
491 next_peer_channel_key: &PublicKey,
492 path_len: usize,
493 private_key: &ChainKeypair,
494 ) -> anyhow::Result<TicketBuilder> {
495 assert!(path_len > 0);
496 let price_per_packet: U256 = 10000000000000000u128.into();
497
498 if path_len > 1 {
499 Ok(TicketBuilder::default()
500 .direction(&private_key.public().to_address(), &next_peer_channel_key.to_address())
501 .amount(price_per_packet.div_f64(1.0)? * U256::from(path_len as u64 - 1))
502 .index(1)
503 .index_offset(1)
504 .win_prob(WinningProbability::ALWAYS)
505 .channel_epoch(1)
506 .challenge(Default::default()))
507 } else {
508 Ok(TicketBuilder::zero_hop()
509 .direction(&private_key.public().to_address(), &next_peer_channel_key.to_address()))
510 }
511 }
512
513 fn create_packet(
514 forward_hops: usize,
515 pseudonym: HoprPseudonym,
516 return_hops: Vec<usize>,
517 msg: &[u8],
518 ) -> anyhow::Result<(HoprPacket, Vec<HoprReplyOpener>)> {
519 assert!((0..=3).contains(&forward_hops), "forward hops must be between 1 and 3");
520 assert!(
521 return_hops.iter().all(|h| (0..=3).contains(h)),
522 "return hops must be between 1 and 3"
523 );
524
525 let ticket = mock_ticket(&PEERS[1].0.public().0, forward_hops + 1, &PEERS[0].0)?;
526 let forward_path = TransportPath::new(PEERS[1..=forward_hops + 1].iter().map(|kp| *kp.1.public()))?;
527
528 let return_paths = return_hops
529 .into_iter()
530 .map(|h| TransportPath::new(PEERS[0..=h].iter().rev().map(|kp| *kp.1.public())))
531 .collect::<std::result::Result<Vec<_>, hopr_path::errors::PathError>>()?;
532
533 Ok(HoprPacket::into_outgoing(
534 msg,
535 &pseudonym,
536 PacketRouting::ForwardPath {
537 forward_path,
538 return_paths,
539 },
540 &PEERS[0].0,
541 ticket,
542 &*MAPPER,
543 &Hash::default(),
544 )?)
545 }
546
547 fn create_packet_from_surb(
548 sender_node: usize,
549 surb_id: HoprSurbId,
550 surb: HoprSurb,
551 hopr_pseudonym: &HoprPseudonym,
552 msg: &[u8],
553 ) -> anyhow::Result<HoprPacket> {
554 assert!((1..=4).contains(&sender_node), "sender_node must be between 1 and 4");
555
556 let ticket = mock_ticket(
557 &PEERS[sender_node - 1].0.public().0,
558 surb.additional_data_receiver.proof_of_relay_values().chain_length() as usize,
559 &PEERS[sender_node].0,
560 )?;
561
562 Ok(HoprPacket::into_outgoing(
563 msg,
564 hopr_pseudonym,
565 PacketRouting::<TransportPath>::Surb(surb_id, surb),
566 &PEERS[sender_node].0,
567 ticket,
568 &*MAPPER,
569 &Hash::default(),
570 )?
571 .0)
572 }
573
574 fn process_packet_at_node<F>(
575 path_len: usize,
576 node_pos: usize,
577 is_reply: bool,
578 packet: HoprPacket,
579 openers: F,
580 ) -> anyhow::Result<HoprPacket>
581 where
582 F: FnMut(&HoprSenderId) -> Option<ReplyOpener>,
583 {
584 assert!((0..=4).contains(&node_pos), "node position must be between 1 and 3");
585
586 let prev_hop = match (node_pos, is_reply) {
587 (1, false) => *PEERS[0].1.public(),
588 (_, false) => *PEERS[node_pos - 1].1.public(),
589 (3, true) => *PEERS[4].1.public(),
590 (_, true) => *PEERS[node_pos + 1].1.public(),
591 };
592
593 let packet = HoprPacket::from_incoming(&packet.to_bytes(), &PEERS[node_pos].1, prev_hop, &*MAPPER, openers)
594 .context(format!("deserialization failure at hop {node_pos}"))?;
595
596 match &packet {
597 HoprPacket::Final(_) => Ok(packet),
598 HoprPacket::Forwarded(_) => {
599 let next_hop = match (node_pos, is_reply) {
600 (3, false) => PEERS[4].0.public().0.clone(),
601 (_, false) => PEERS[node_pos + 1].0.public().0.clone(),
602 (1, true) => PEERS[0].0.public().0.clone(),
603 (_, true) => PEERS[node_pos - 1].0.public().0.clone(),
604 };
605
606 let next_ticket = mock_ticket(&next_hop, path_len, &PEERS[node_pos].0)?;
607 Ok(forward(
608 packet.clone(),
609 &PEERS[node_pos].0,
610 next_ticket,
611 &Hash::default(),
612 ))
613 }
614 HoprPacket::Outgoing(_) => bail!("invalid packet state"),
615 }
616 }
617
618 #[parameterized(hops = { 0,1,2,3 })]
619 fn test_packet_forward_message_no_surb(hops: usize) -> anyhow::Result<()> {
620 let msg = b"some testing forward message";
621 let pseudonym = SimplePseudonym::random();
622 let (mut packet, opener) = create_packet(hops, pseudonym, vec![], msg)?;
623
624 assert!(opener.is_empty());
625 match &packet {
626 HoprPacket::Outgoing { .. } => {}
627 _ => bail!("invalid packet initial state"),
628 }
629
630 let mut actual_plain_text = Box::default();
631 for hop in 1..=hops + 1 {
632 packet = process_packet_at_node(hops + 1, hop, false, packet, |_| None)
633 .context(format!("packet decoding failed at hop {hop}"))?;
634
635 match &packet {
636 HoprPacket::Final(packet) => {
637 assert_eq!(hop - 1, hops, "final packet must be at the last hop");
638 assert!(packet.ack_key.is_some(), "must not be a no-ack packet");
639 actual_plain_text = packet.plain_text.clone();
640 }
641 HoprPacket::Forwarded(fwd) => {
642 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
643 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
644 assert_eq!(hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
645 }
646 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
647 }
648 }
649
650 assert_eq!(actual_plain_text.as_ref(), msg, "invalid plaintext");
651 Ok(())
652 }
653
654 #[parameterized(forward_hops = { 0,1,2,3 }, return_hops = { 0, 1, 2, 3})]
655 fn test_packet_forward_message_with_surb(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
656 let msg = b"some testing forward message";
657 let pseudonym = SimplePseudonym::random();
658 let (mut packet, openers) = create_packet(forward_hops, pseudonym, vec![return_hops], msg)?;
659
660 assert_eq!(1, openers.len(), "invalid number of openers");
661 match &packet {
662 HoprPacket::Outgoing { .. } => {}
663 _ => bail!("invalid packet initial state"),
664 }
665
666 let mut received_plain_text = Box::default();
667 let mut received_surbs = vec![];
668 for hop in 1..=forward_hops + 1 {
669 packet = process_packet_at_node(forward_hops + 1, hop, false, packet, |_| None)
670 .context(format!("packet decoding failed at hop {hop}"))?;
671
672 match &packet {
673 HoprPacket::Final(packet) => {
674 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
675 assert_eq!(pseudonym, packet.sender, "invalid sender");
676 assert!(packet.ack_key.is_some(), "must not be a no-ack packet");
677 received_plain_text = packet.plain_text.clone();
678 received_surbs.extend(packet.surbs.clone());
679 }
680 HoprPacket::Forwarded(fwd) => {
681 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
682 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
683 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
684 }
685 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
686 }
687 }
688
689 assert_eq!(received_plain_text.as_ref(), msg, "invalid plaintext");
690 assert_eq!(1, received_surbs.len(), "invalid number of surbs");
691 assert_eq!(
692 return_hops as u8 + 1,
693 received_surbs[0]
694 .1
695 .additional_data_receiver
696 .proof_of_relay_values()
697 .chain_length(),
698 "surb has invalid por chain length"
699 );
700
701 Ok(())
702 }
703
704 #[parameterized(
705 forward_hops = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 },
706 return_hops = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 }
707 )]
708 fn test_packet_forward_and_reply_message(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
709 let pseudonym = SimplePseudonym::random();
710
711 let fwd_msg = b"some testing forward message";
713 let (mut fwd_packet, mut openers) = create_packet(forward_hops, pseudonym, vec![return_hops], fwd_msg)?;
714
715 assert_eq!(1, openers.len(), "invalid number of openers");
716 match &fwd_packet {
717 HoprPacket::Outgoing { .. } => {}
718 _ => bail!("invalid packet initial state"),
719 }
720
721 let mut received_fwd_plain_text = Box::default();
722 let mut received_surbs = vec![];
723 for hop in 1..=forward_hops + 1 {
724 fwd_packet = process_packet_at_node(forward_hops + 1, hop, false, fwd_packet, |_| None)
725 .context(format!("packet decoding failed at hop {hop}"))?;
726
727 match &fwd_packet {
728 HoprPacket::Final(incoming) => {
729 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
730 assert_eq!(pseudonym, incoming.sender, "invalid sender");
731 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
732 received_fwd_plain_text = incoming.plain_text.clone();
733 received_surbs.extend(incoming.surbs.clone());
734 }
735 HoprPacket::Forwarded(fwd) => {
736 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
737 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
738 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
739 }
740 HoprPacket::Outgoing { .. } => bail!("invalid packet state at hop {hop}"),
741 }
742 }
743
744 assert_eq!(received_fwd_plain_text.as_ref(), fwd_msg, "invalid plaintext");
745 assert_eq!(1, received_surbs.len(), "invalid number of surbs");
746 assert_eq!(
747 return_hops as u8 + 1,
748 received_surbs[0]
749 .1
750 .additional_data_receiver
751 .proof_of_relay_values()
752 .chain_length(),
753 "surb has invalid por chain length"
754 );
755
756 let re_msg = b"some testing reply message";
758 let mut re_packet = create_packet_from_surb(
759 forward_hops + 1,
760 received_surbs[0].0,
761 received_surbs[0].1.clone(),
762 &pseudonym,
763 re_msg,
764 )?;
765
766 let mut openers_fn = |p: &HoprSenderId| {
767 assert_eq!(p.pseudonym(), pseudonym);
768 let opener = openers.pop();
769 assert!(opener.as_ref().is_none_or(|(id, _)| id == &p.surb_id()));
770 opener.map(|(_, opener)| opener)
771 };
772
773 match &re_packet {
774 HoprPacket::Outgoing { .. } => {}
775 _ => bail!("invalid packet initial state"),
776 }
777
778 let mut received_re_plain_text = Box::default();
779 for hop in (0..=return_hops).rev() {
780 re_packet = process_packet_at_node(return_hops + 1, hop, true, re_packet, &mut openers_fn)
781 .context(format!("packet decoding failed at hop {hop}"))?;
782
783 match &re_packet {
784 HoprPacket::Final(incoming) => {
785 assert_eq!(hop, 0, "final packet must be at the last hop");
786 assert_eq!(pseudonym, incoming.sender, "invalid sender");
787 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
788 assert!(incoming.surbs.is_empty(), "must not receive surbs on reply");
789 received_re_plain_text = incoming.plain_text.clone();
790 }
791 HoprPacket::Forwarded(fwd) => {
792 assert_eq!(PEERS[hop + 1].1.public(), &fwd.previous_hop, "invalid previous hop");
793 assert_eq!(PEERS[hop - 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
794 assert_eq!(hop, fwd.path_pos as usize, "invalid path position");
795 }
796 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
797 }
798 }
799
800 assert_eq!(received_re_plain_text.as_ref(), re_msg, "invalid plaintext");
801 Ok(())
802 }
803
804 #[parameterized(
805 forward_hops = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 },
806 return_hops = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 }
807 )]
808 fn test_packet_surbs_only_and_reply_message(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
809 let pseudonym = SimplePseudonym::random();
810
811 let (mut fwd_packet, mut openers) = create_packet(forward_hops, pseudonym, vec![return_hops; 2], &[])?;
813
814 assert_eq!(2, openers.len(), "invalid number of openers");
815 match &fwd_packet {
816 HoprPacket::Outgoing { .. } => {}
817 _ => bail!("invalid packet initial state"),
818 }
819
820 let mut received_surbs = vec![];
821 for hop in 1..=forward_hops + 1 {
822 fwd_packet = process_packet_at_node(forward_hops + 1, hop, false, fwd_packet, |_| None)
823 .context(format!("packet decoding failed at hop {hop}"))?;
824
825 match &fwd_packet {
826 HoprPacket::Final(incoming) => {
827 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
828 assert!(
829 incoming.plain_text.is_empty(),
830 "must not receive plaintext on surbs only packet"
831 );
832 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
833 assert_eq!(2, incoming.surbs.len(), "invalid number of received surbs per packet");
834 assert_eq!(pseudonym, incoming.sender, "invalid sender");
835 received_surbs.extend(incoming.surbs.clone());
836 }
837 HoprPacket::Forwarded(fwd) => {
838 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
839 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
840 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
841 }
842 HoprPacket::Outgoing { .. } => bail!("invalid packet state at hop {hop}"),
843 }
844 }
845
846 assert_eq!(2, received_surbs.len(), "invalid number of surbs");
847 for recv_surb in &received_surbs {
848 assert_eq!(
849 return_hops as u8 + 1,
850 recv_surb
851 .1
852 .additional_data_receiver
853 .proof_of_relay_values()
854 .chain_length(),
855 "surb has invalid por chain length"
856 );
857 }
858
859 let mut openers_fn = |p: &HoprSenderId| {
860 assert_eq!(p.pseudonym(), pseudonym);
861 let (id, opener) = openers.remove(0);
862 assert_eq!(id, p.surb_id());
863 Some(opener)
864 };
865
866 for (i, recv_surb) in received_surbs.into_iter().enumerate() {
868 let re_msg = format!("some testing reply message {i}");
869 let mut re_packet = create_packet_from_surb(
870 forward_hops + 1,
871 recv_surb.0,
872 recv_surb.1,
873 &pseudonym,
874 re_msg.as_bytes(),
875 )?;
876
877 match &re_packet {
878 HoprPacket::Outgoing { .. } => {}
879 _ => bail!("invalid packet initial state in reply {i}"),
880 }
881
882 let mut received_re_plain_text = Box::default();
883 for hop in (0..=return_hops).rev() {
884 re_packet = process_packet_at_node(return_hops + 1, hop, true, re_packet, &mut openers_fn)
885 .context(format!("packet decoding failed at hop {hop} in reply {i}"))?;
886
887 match &re_packet {
888 HoprPacket::Final(incoming) => {
889 assert_eq!(hop, 0, "final packet must be at the last hop for reply {i}");
890 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
891 assert!(
892 incoming.surbs.is_empty(),
893 "must not receive surbs on reply for reply {i}"
894 );
895 received_re_plain_text = incoming.plain_text.clone();
896 }
897 HoprPacket::Forwarded(fwd) => {
898 assert_eq!(
899 PEERS[hop + 1].1.public(),
900 &fwd.previous_hop,
901 "invalid previous hop in reply {i}"
902 );
903 assert_eq!(
904 PEERS[hop - 1].1.public(),
905 &fwd.outgoing.next_hop,
906 "invalid next hop in reply {i}"
907 );
908 assert_eq!(hop, fwd.path_pos as usize, "invalid path position in reply {i}");
909 }
910 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop} in reply {i}"),
911 }
912 }
913
914 assert_eq!(
915 received_re_plain_text.as_ref(),
916 re_msg.as_bytes(),
917 "invalid plaintext in reply {i}"
918 );
919 }
920 Ok(())
921 }
922}