1use std::fmt::{Display, Formatter};
2
3use hopr_crypto_sphinx::prelude::*;
4use hopr_crypto_types::prelude::*;
5use hopr_internal_types::prelude::*;
6use hopr_primitive_types::prelude::*;
7#[cfg(feature = "rayon")]
8use rayon::prelude::*;
9
10use crate::{
11 HoprPseudonym, HoprReplyOpener, HoprSphinxHeaderSpec, HoprSphinxSuite, HoprSurb, PAYLOAD_SIZE_INT,
12 errors::{
13 PacketError::{PacketConstructionError, PacketDecodingError},
14 Result,
15 },
16 por::{
17 ProofOfRelayString, ProofOfRelayValues, SurbReceiverInfo, derive_ack_key_share, generate_proof_of_relay,
18 pre_verify,
19 },
20 types::{HoprPacketMessage, HoprPacketParts, HoprSenderId, HoprSurbId, PacketSignals},
21};
22
23#[derive(Clone)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct PartialHoprPacket {
34 partial_packet: PartialPacket<HoprSphinxSuite, HoprSphinxHeaderSpec>,
35 surbs: Vec<HoprSurb>,
36 openers: Vec<HoprReplyOpener>,
37 ticket: Ticket,
38 next_hop: OffchainPublicKey,
39 ack_challenge: HalfKeyChallenge,
40}
41
42struct PathKeyData {
46 pub shared_keys: SharedKeys<<HoprSphinxSuite as SphinxSuite>::E, <HoprSphinxSuite as SphinxSuite>::G>,
48 pub por_strings: Vec<ProofOfRelayString>,
50 pub por_values: ProofOfRelayValues,
52}
53
54impl PathKeyData {
55 fn new(path: &[OffchainPublicKey]) -> Result<Self> {
56 let shared_keys = HoprSphinxSuite::new_shared_keys(path)?;
57 let (por_strings, por_values) = generate_proof_of_relay(&shared_keys.secrets)?;
58
59 Ok(Self {
60 shared_keys,
61 por_strings,
62 por_values,
63 })
64 }
65
66 fn iter_from_paths(paths: Vec<&[OffchainPublicKey]>) -> Result<impl Iterator<Item = Self>> {
70 #[cfg(not(feature = "rayon"))]
71 let paths = paths.into_iter();
72
73 #[cfg(feature = "rayon")]
74 let paths = paths.into_par_iter();
75
76 paths
77 .map(Self::new)
78 .collect::<Result<Vec<_>>>()
79 .map(|paths| paths.into_iter())
80 }
81}
82
83impl PartialHoprPacket {
84 pub fn new<M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>, P: NonEmptyPath<OffchainPublicKey> + Send>(
95 pseudonym: &HoprPseudonym,
96 routing: PacketRouting<P>,
97 chain_keypair: &ChainKeypair,
98 ticket: TicketBuilder,
99 mapper: &M,
100 domain_separator: &Hash,
101 ) -> Result<Self> {
102 match routing {
103 PacketRouting::ForwardPath {
104 forward_path,
105 return_paths,
106 } => {
107 let mut key_data = PathKeyData::iter_from_paths(
109 std::iter::once(forward_path.hops())
110 .chain(return_paths.iter().map(|p| p.hops()))
111 .collect(),
112 )?;
113
114 let PathKeyData {
115 shared_keys,
116 por_strings,
117 por_values,
118 } = key_data
119 .next()
120 .ok_or_else(|| PacketConstructionError("empty path".into()))?;
121
122 let receiver_data = HoprSenderId::new(pseudonym);
123
124 let (surbs, openers): (Vec<_>, Vec<_>) = key_data
128 .zip(return_paths)
129 .zip(receiver_data.into_sequence())
130 .map(|((key_data, rp), data)| create_surb_for_path((rp, key_data), data, mapper))
131 .collect::<Result<Vec<_>>>()?
132 .into_iter()
133 .unzip();
134
135 let ticket = ticket
137 .eth_challenge(por_values.ticket_challenge())
138 .build_signed(chain_keypair, domain_separator)?
139 .leak();
140
141 Ok(Self {
142 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
143 MetaPacketRouting::ForwardPath {
144 shared_keys,
145 forward_path: &forward_path,
146 receiver_data: &receiver_data,
147 additional_data_relayer: &por_strings,
148 no_ack: false,
149 },
150 mapper,
151 )?,
152 surbs,
153 openers,
154 ticket,
155 next_hop: forward_path[0],
156 ack_challenge: por_values.acknowledgement_challenge(),
157 })
158 }
159 PacketRouting::Surb(id, surb) => {
160 let ticket = ticket
162 .eth_challenge(surb.additional_data_receiver.proof_of_relay_values().ticket_challenge())
163 .build_signed(chain_keypair, domain_separator)?
164 .leak();
165
166 Ok(Self {
167 ticket,
168 next_hop: mapper.map_id_to_public(&surb.first_relayer).ok_or_else(|| {
169 PacketConstructionError(format!(
170 "failed to map key id {} to public key",
171 surb.first_relayer.to_hex()
172 ))
173 })?,
174 ack_challenge: surb
175 .additional_data_receiver
176 .proof_of_relay_values()
177 .acknowledgement_challenge(),
178 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
179 MetaPacketRouting::Surb(surb, &HoprSenderId::from_pseudonym_and_id(pseudonym, id)),
180 mapper,
181 )?,
182 surbs: vec![],
183 openers: vec![],
184 })
185 }
186 PacketRouting::NoAck(destination) => {
187 let PathKeyData {
189 shared_keys,
190 por_strings,
191 por_values,
192 ..
193 } = PathKeyData::new(&[destination])?;
194
195 let ticket = ticket
197 .eth_challenge(por_values.ticket_challenge())
198 .build_signed(chain_keypair, domain_separator)?
199 .leak();
200
201 Ok(Self {
202 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
203 MetaPacketRouting::ForwardPath {
204 shared_keys,
205 forward_path: &[destination],
206 receiver_data: &HoprSenderId::new(pseudonym),
207 additional_data_relayer: &por_strings,
208 no_ack: true, },
210 mapper,
211 )?,
212 ticket,
213 next_hop: destination,
214 ack_challenge: por_values.acknowledgement_challenge(),
215 surbs: vec![],
216 openers: vec![],
217 })
218 }
219 }
220 }
221
222 pub fn into_hopr_packet<S: Into<PacketSignals>>(
227 self,
228 msg: &[u8],
229 signals: S,
230 ) -> Result<(HoprPacket, Vec<HoprReplyOpener>)> {
231 let msg = HoprPacketMessage::try_from(HoprPacketParts {
232 surbs: self.surbs,
233 payload: msg.into(),
234 signals: signals.into(),
235 })?;
236 Ok((
237 HoprPacket::Outgoing(
238 HoprOutgoingPacket {
239 packet: self.partial_packet.into_meta_packet(msg.into()),
240 ticket: self.ticket,
241 next_hop: self.next_hop,
242 ack_challenge: self.ack_challenge,
243 }
244 .into(),
245 ),
246 self.openers,
247 ))
248 }
249}
250
251#[derive(Clone)]
253pub struct HoprIncomingPacket {
254 pub packet_tag: PacketTag,
256 pub ack_key: Option<HalfKey>,
261 pub previous_hop: OffchainPublicKey,
263 pub plain_text: Box<[u8]>,
265 pub sender: HoprPseudonym,
267 pub surbs: Vec<(HoprSurbId, HoprSurb)>,
269 pub signals: PacketSignals,
273}
274
275impl std::fmt::Debug for HoprIncomingPacket {
276 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
277 f.debug_struct("HoprIncomingPacket")
278 .field("packet_tag", &self.packet_tag)
279 .field("ack_key", &self.ack_key)
280 .field("previous_hop", &self.previous_hop)
281 .field("sender", &self.sender)
282 .field("signals", &self.signals)
283 .finish_non_exhaustive()
284 }
285}
286
287#[derive(Clone)]
289pub struct HoprOutgoingPacket {
290 pub packet: MetaPacket<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>,
292 pub ticket: Ticket,
294 pub next_hop: OffchainPublicKey,
296 pub ack_challenge: HalfKeyChallenge,
298}
299
300impl std::fmt::Debug for HoprOutgoingPacket {
301 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
302 f.debug_struct("HoprOutgoingPacket")
303 .field("ticket", &self.ticket)
304 .field("next_hop", &self.next_hop)
305 .field("ack_challenge", &self.ack_challenge)
306 .finish_non_exhaustive()
307 }
308}
309
310#[derive(Clone)]
312pub struct HoprForwardedPacket {
313 pub outgoing: HoprOutgoingPacket,
315 pub packet_tag: PacketTag,
317 pub ack_key: HalfKey,
319 pub previous_hop: OffchainPublicKey,
321 pub own_key: HalfKey,
323 pub next_challenge: EthereumChallenge,
325 pub path_pos: u8,
327}
328
329impl std::fmt::Debug for HoprForwardedPacket {
330 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
331 f.debug_struct("HoprForwardedPacket")
332 .field("outgoing", &self.outgoing)
333 .field("packet_tag", &hex::encode(self.packet_tag))
334 .field("ack_key", &self.ack_key)
335 .field("previous_hop", &self.previous_hop)
336 .field("own_key", &self.own_key)
337 .field("next_challenge", &self.next_challenge)
338 .field("path_pos", &self.path_pos)
339 .finish_non_exhaustive()
340 }
341}
342
343#[derive(Clone, Debug, strum::EnumTryAs, strum::EnumIs)]
349pub enum HoprPacket {
350 Final(Box<HoprIncomingPacket>),
352 Forwarded(Box<HoprForwardedPacket>),
354 Outgoing(Box<HoprOutgoingPacket>),
356}
357
358impl HoprPacket {
359 pub fn packet_tag(&self) -> Option<&PacketTag> {
361 match self {
362 HoprPacket::Final(packet) => Some(&packet.packet_tag),
363 HoprPacket::Forwarded(packet) => Some(&packet.packet_tag),
364 HoprPacket::Outgoing(_) => None,
365 }
366 }
367}
368
369impl Display for HoprPacket {
370 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
371 match &self {
372 Self::Final(_) => write!(f, "Final"),
373 Self::Forwarded(_) => write!(f, "Forwarded"),
374 Self::Outgoing(_) => write!(f, "Outgoing"),
375 }
376 }
377}
378
379#[derive(Clone)]
381pub enum PacketRouting<P: NonEmptyPath<OffchainPublicKey> = TransportPath> {
382 ForwardPath { forward_path: P, return_paths: Vec<P> },
386 Surb(HoprSurbId, HoprSurb),
388 NoAck(OffchainPublicKey),
391}
392
393fn create_surb_for_path<M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>, P: NonEmptyPath<OffchainPublicKey>>(
394 return_path: (P, PathKeyData),
395 recv_data: HoprSenderId,
396 mapper: &M,
397) -> Result<(HoprSurb, HoprReplyOpener)> {
398 let (
399 return_path,
400 PathKeyData {
401 shared_keys,
402 por_strings,
403 por_values,
404 },
405 ) = return_path;
406
407 Ok(create_surb::<HoprSphinxSuite, HoprSphinxHeaderSpec>(
408 shared_keys,
409 &return_path
410 .iter()
411 .map(|k| {
412 mapper
413 .map_key_to_id(k)
414 .ok_or_else(|| PacketConstructionError(format!("failed to map key {} to id", k.to_hex())))
415 })
416 .collect::<Result<Vec<_>>>()?,
417 &por_strings,
418 recv_data,
419 SurbReceiverInfo::new(por_values, [0u8; 32]),
420 )
421 .map(|(s, r)| (s, (recv_data.surb_id(), r)))?)
422}
423
424impl HoprPacket {
425 pub const MAX_SURBS_IN_PACKET: usize = HoprPacket::PAYLOAD_SIZE / HoprSurb::SIZE;
427 pub const PAYLOAD_SIZE: usize = PAYLOAD_SIZE_INT - HoprPacketMessage::HEADER_LEN;
431 pub const SIZE: usize =
433 MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN + Ticket::SIZE;
434
435 #[allow(clippy::too_many_arguments)] pub fn into_outgoing<
451 M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
452 P: NonEmptyPath<OffchainPublicKey> + Send,
453 S: Into<PacketSignals>,
454 >(
455 msg: &[u8],
456 pseudonym: &HoprPseudonym,
457 routing: PacketRouting<P>,
458 chain_keypair: &ChainKeypair,
459 ticket: TicketBuilder,
460 mapper: &M,
461 domain_separator: &Hash,
462 signals: S,
463 ) -> Result<(Self, Vec<HoprReplyOpener>)> {
464 PartialHoprPacket::new(pseudonym, routing, chain_keypair, ticket, mapper, domain_separator)?
465 .into_hopr_packet(msg, signals)
466 }
467
468 pub const fn max_surbs_with_message(msg_len: usize) -> usize {
471 HoprPacket::PAYLOAD_SIZE.saturating_sub(msg_len) / HoprSurb::SIZE
472 }
473
474 pub const fn max_message_with_surbs(num_surbs: usize) -> usize {
477 HoprPacket::PAYLOAD_SIZE.saturating_sub(num_surbs * HoprSurb::SIZE)
478 }
479
480 pub fn from_incoming<M, F>(
483 data: &[u8],
484 node_keypair: &OffchainKeypair,
485 previous_hop: OffchainPublicKey,
486 mapper: &M,
487 reply_openers: F,
488 ) -> Result<Self>
489 where
490 M: KeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
491 F: FnMut(&HoprSenderId) -> Option<ReplyOpener>,
492 {
493 if data.len() == Self::SIZE {
494 let (pre_packet, pre_ticket) =
495 data.split_at(MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN);
496
497 let mp: MetaPacket<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT> =
498 MetaPacket::try_from(pre_packet)?;
499
500 match mp.into_forwarded(node_keypair, mapper, reply_openers)? {
501 ForwardedMetaPacket::Relayed {
502 packet,
503 derived_secret,
504 additional_info,
505 packet_tag,
506 next_node,
507 path_pos,
508 ..
509 } => {
510 let ack_key = derive_ack_key_share(&derived_secret);
511
512 let ticket = Ticket::try_from(pre_ticket)?;
513 let verification_output = pre_verify(&derived_secret, &additional_info, &ticket.challenge)?;
514 Ok(Self::Forwarded(
515 HoprForwardedPacket {
516 outgoing: HoprOutgoingPacket {
517 packet,
518 ticket,
519 next_hop: next_node,
520 ack_challenge: verification_output.ack_challenge,
521 },
522 packet_tag,
523 ack_key,
524 previous_hop,
525 path_pos,
526 own_key: verification_output.own_key,
527 next_challenge: verification_output.next_ticket_challenge,
528 }
529 .into(),
530 ))
531 }
532 ForwardedMetaPacket::Final {
533 packet_tag,
534 plain_text,
535 derived_secret,
536 receiver_data,
537 no_ack,
538 } => {
539 let HoprPacketParts {
541 surbs,
542 payload,
543 signals,
544 } = HoprPacketMessage::from(plain_text).try_into()?;
545 let should_acknowledge = !no_ack;
546 tracing::trace!(?should_acknowledge, "acknowledgement for final packet");
547 Ok(Self::Final(
548 HoprIncomingPacket {
549 packet_tag,
550 ack_key: should_acknowledge.then(|| derive_ack_key_share(&derived_secret)),
551 previous_hop,
552 plain_text: payload.into(),
553 surbs: receiver_data.into_sequence().map(|d| d.surb_id()).zip(surbs).collect(),
554 sender: receiver_data.pseudonym(),
555 signals,
556 }
557 .into(),
558 ))
559 }
560 }
561 } else {
562 Err(PacketDecodingError("packet has invalid size".into()))
563 }
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use anyhow::{Context, bail};
570 use bimap::BiHashMap;
571 use hex_literal::hex;
572 use hopr_crypto_random::Randomizable;
573 use parameterized::parameterized;
574
575 use super::*;
576 use crate::types::PacketSignal;
577
578 lazy_static::lazy_static! {
579 static ref PEERS: [(ChainKeypair, OffchainKeypair); 5] = [
580 (hex!("a7c486ceccf5ab53bd428888ab1543dc2667abd2d5e80aae918da8d4b503a426"), hex!("5eb212d4d6aa5948c4f71574d45dad43afef6d330edb873fca69d0e1b197e906")),
581 (hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"), hex!("e995db483ada5174666c46bafbf3628005aca449c94ebdc0c9239c3f65d61ae0")),
582 (hex!("ca4bdfd54a8467b5283a0216288fdca7091122479ccf3cfb147dfa59d13f3486"), hex!("9dec751c00f49e50fceff7114823f726a0425a68a8dc6af0e4287badfea8f4a4")),
583 (hex!("e306ebfb0d01d0da0952c9a567d758093a80622c6cb55052bf5f1a6ebd8d7b5c"), hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed")),
584 (hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775"), hex!("e0bf93e9c916104da00b1850adc4608bd7e9087bbd3f805451f4556aa6b3fd6e")),
585 ].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")));
586
587 static ref MAPPER: bimap::BiMap<KeyIdent, OffchainPublicKey> = PEERS
588 .iter()
589 .enumerate()
590 .map(|(i, (_, k))| (KeyIdent::from(i as u32), *k.public()))
591 .collect::<BiHashMap<_, _>>();
592 }
593
594 fn forward(
595 mut packet: HoprPacket,
596 chain_keypair: &ChainKeypair,
597 next_ticket: TicketBuilder,
598 domain_separator: &Hash,
599 ) -> HoprPacket {
600 if let HoprPacket::Forwarded(fwd) = &mut packet {
601 fwd.outgoing.ticket = next_ticket
602 .eth_challenge(fwd.next_challenge)
603 .build_signed(chain_keypair, domain_separator)
604 .expect("ticket should create")
605 .leak();
606 }
607
608 packet
609 }
610
611 impl HoprPacket {
612 pub fn to_bytes(&self) -> Box<[u8]> {
613 let dummy_ticket = hex!("67f0ca18102feec505e5bfedcc25963e9c64a6f8a250adcad7d2830dd607585700000000000000000000000000000000000000000000000000000000000000003891bf6fd4a78e868fc7ad477c09b16fc70dd01ea67e18264d17e3d04f6d8576de2e6472b0072e510df6e9fa1dfcc2727cc7633edfeb9ec13860d9ead29bee71d68de3736c2f7a9f42de76ccd57a5f5847bc7349");
614 let (packet, ticket) = match self {
615 Self::Final(packet) => (packet.plain_text.clone(), dummy_ticket.as_ref().into()),
616 Self::Forwarded(fwd) => (
617 Vec::from(fwd.outgoing.packet.as_ref()).into_boxed_slice(),
618 fwd.outgoing.ticket.clone().into_boxed(),
619 ),
620 Self::Outgoing(out) => (
621 Vec::from(out.packet.as_ref()).into_boxed_slice(),
622 out.ticket.clone().into_boxed(),
623 ),
624 };
625
626 let mut ret = Vec::with_capacity(Self::SIZE);
627 ret.extend_from_slice(packet.as_ref());
628 ret.extend_from_slice(&ticket);
629 ret.into_boxed_slice()
630 }
631 }
632
633 fn mock_ticket(next_peer_channel_key: &PublicKey, path_len: usize) -> anyhow::Result<TicketBuilder> {
634 assert!(path_len > 0);
635 let price_per_packet: U256 = 10000000000000000u128.into();
636
637 if path_len > 1 {
638 Ok(TicketBuilder::default()
639 .counterparty(next_peer_channel_key.to_address())
640 .amount(price_per_packet.div_f64(1.0)? * U256::from(path_len as u64 - 1))
641 .index(1)
642 .win_prob(WinningProbability::ALWAYS)
643 .channel_epoch(1)
644 .eth_challenge(Default::default()))
645 } else {
646 Ok(TicketBuilder::zero_hop().counterparty(next_peer_channel_key.to_address()))
647 }
648 }
649
650 const FLAGS: PacketSignal = PacketSignal::OutOfSurbs;
651
652 fn create_packet(
653 forward_hops: usize,
654 pseudonym: HoprPseudonym,
655 return_hops: Vec<usize>,
656 msg: &[u8],
657 ) -> anyhow::Result<(HoprPacket, Vec<HoprReplyOpener>)> {
658 assert!((0..=3).contains(&forward_hops), "forward hops must be between 1 and 3");
659 assert!(
660 return_hops.iter().all(|h| (0..=3).contains(h)),
661 "return hops must be between 1 and 3"
662 );
663
664 let ticket = mock_ticket(&PEERS[1].0.public(), forward_hops + 1)?;
665 let forward_path = TransportPath::new(PEERS[1..=forward_hops + 1].iter().map(|kp| *kp.1.public()))?;
666
667 let return_paths = return_hops
668 .into_iter()
669 .map(|h| TransportPath::new(PEERS[0..=h].iter().rev().map(|kp| *kp.1.public())))
670 .collect::<std::result::Result<Vec<_>, hopr_internal_types::errors::PathError>>()?;
671
672 Ok(HoprPacket::into_outgoing(
673 msg,
674 &pseudonym,
675 PacketRouting::ForwardPath {
676 forward_path,
677 return_paths,
678 },
679 &PEERS[0].0,
680 ticket,
681 &*MAPPER,
682 &Hash::default(),
683 FLAGS,
684 )?)
685 }
686
687 fn create_packet_from_surb(
688 sender_node: usize,
689 surb_id: HoprSurbId,
690 surb: HoprSurb,
691 hopr_pseudonym: &HoprPseudonym,
692 msg: &[u8],
693 ) -> anyhow::Result<HoprPacket> {
694 assert!((1..=4).contains(&sender_node), "sender_node must be between 1 and 4");
695
696 let ticket = mock_ticket(
697 &PEERS[sender_node - 1].0.public(),
698 surb.additional_data_receiver.proof_of_relay_values().chain_length() as usize,
699 )?;
700
701 Ok(HoprPacket::into_outgoing(
702 msg,
703 hopr_pseudonym,
704 PacketRouting::<TransportPath>::Surb(surb_id, surb),
705 &PEERS[sender_node].0,
706 ticket,
707 &*MAPPER,
708 &Hash::default(),
709 FLAGS,
710 )?
711 .0)
712 }
713
714 fn process_packet_at_node<F>(
715 path_len: usize,
716 node_pos: usize,
717 is_reply: bool,
718 packet: HoprPacket,
719 openers: F,
720 ) -> anyhow::Result<HoprPacket>
721 where
722 F: FnMut(&HoprSenderId) -> Option<ReplyOpener>,
723 {
724 assert!((0..=4).contains(&node_pos), "node position must be between 1 and 3");
725
726 let prev_hop = match (node_pos, is_reply) {
727 (1, false) => *PEERS[0].1.public(),
728 (_, false) => *PEERS[node_pos - 1].1.public(),
729 (3, true) => *PEERS[4].1.public(),
730 (_, true) => *PEERS[node_pos + 1].1.public(),
731 };
732
733 let packet = HoprPacket::from_incoming(&packet.to_bytes(), &PEERS[node_pos].1, prev_hop, &*MAPPER, openers)
734 .context(format!("deserialization failure at hop {node_pos}"))?;
735
736 match &packet {
737 HoprPacket::Final(_) => Ok(packet),
738 HoprPacket::Forwarded(_) => {
739 let next_hop = match (node_pos, is_reply) {
740 (3, false) => PEERS[4].0.public().clone(),
741 (_, false) => PEERS[node_pos + 1].0.public().clone(),
742 (1, true) => PEERS[0].0.public().clone(),
743 (_, true) => PEERS[node_pos - 1].0.public().clone(),
744 };
745
746 let next_ticket = mock_ticket(&next_hop, path_len)?;
747 Ok(forward(
748 packet.clone(),
749 &PEERS[node_pos].0,
750 next_ticket,
751 &Hash::default(),
752 ))
753 }
754 HoprPacket::Outgoing(_) => bail!("invalid packet state"),
755 }
756 }
757
758 #[parameterized(hops = { 0,1,2,3 })]
759 fn test_packet_forward_message_no_surb(hops: usize) -> anyhow::Result<()> {
760 let msg = b"some testing forward message";
761 let pseudonym = SimplePseudonym::random();
762 let (mut packet, opener) = create_packet(hops, pseudonym, vec![], msg)?;
763
764 assert!(opener.is_empty());
765 match &packet {
766 HoprPacket::Outgoing { .. } => {}
767 _ => bail!("invalid packet initial state"),
768 }
769
770 let mut actual_plain_text = Box::default();
771 for hop in 1..=hops + 1 {
772 packet = process_packet_at_node(hops + 1, hop, false, packet, |_| None)
773 .context(format!("packet decoding failed at hop {hop}"))?;
774
775 match &packet {
776 HoprPacket::Final(packet) => {
777 assert_eq!(hop - 1, hops, "final packet must be at the last hop");
778 assert!(packet.ack_key.is_some(), "must not be a no-ack packet");
779 assert_eq!(PacketSignals::from(FLAGS), packet.signals);
780 actual_plain_text = packet.plain_text.clone();
781 }
782 HoprPacket::Forwarded(fwd) => {
783 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
784 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
785 assert_eq!(hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
786 }
787 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
788 }
789 }
790
791 assert_eq!(actual_plain_text.as_ref(), msg, "invalid plaintext");
792 Ok(())
793 }
794
795 #[parameterized(forward_hops = { 0,1,2,3 }, return_hops = { 0, 1, 2, 3})]
796 fn test_packet_forward_message_with_surb(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
797 let msg = b"some testing forward message";
798 let pseudonym = SimplePseudonym::random();
799 let (mut packet, openers) = create_packet(forward_hops, pseudonym, vec![return_hops], msg)?;
800
801 assert_eq!(1, openers.len(), "invalid number of openers");
802 match &packet {
803 HoprPacket::Outgoing { .. } => {}
804 _ => bail!("invalid packet initial state"),
805 }
806
807 let mut received_plain_text = Box::default();
808 let mut received_surbs = vec![];
809 for hop in 1..=forward_hops + 1 {
810 packet = process_packet_at_node(forward_hops + 1, hop, false, packet, |_| None)
811 .context(format!("packet decoding failed at hop {hop}"))?;
812
813 match &packet {
814 HoprPacket::Final(packet) => {
815 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
816 assert_eq!(pseudonym, packet.sender, "invalid sender");
817 assert!(packet.ack_key.is_some(), "must not be a no-ack packet");
818 assert_eq!(PacketSignals::from(FLAGS), packet.signals);
819 received_plain_text = packet.plain_text.clone();
820 received_surbs.extend(packet.surbs.clone());
821 }
822 HoprPacket::Forwarded(fwd) => {
823 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
824 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
825 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
826 }
827 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
828 }
829 }
830
831 assert_eq!(received_plain_text.as_ref(), msg, "invalid plaintext");
832 assert_eq!(1, received_surbs.len(), "invalid number of surbs");
833 assert_eq!(
834 return_hops as u8 + 1,
835 received_surbs[0]
836 .1
837 .additional_data_receiver
838 .proof_of_relay_values()
839 .chain_length(),
840 "surb has invalid por chain length"
841 );
842
843 Ok(())
844 }
845
846 #[parameterized(
847 forward_hops = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 },
848 return_hops = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 }
849 )]
850 fn test_packet_forward_and_reply_message(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
851 let pseudonym = SimplePseudonym::random();
852
853 let fwd_msg = b"some testing forward message";
855 let (mut fwd_packet, mut openers) = create_packet(forward_hops, pseudonym, vec![return_hops], fwd_msg)?;
856
857 assert_eq!(1, openers.len(), "invalid number of openers");
858 match &fwd_packet {
859 HoprPacket::Outgoing { .. } => {}
860 _ => bail!("invalid packet initial state"),
861 }
862
863 let mut received_fwd_plain_text = Box::default();
864 let mut received_surbs = vec![];
865 for hop in 1..=forward_hops + 1 {
866 fwd_packet = process_packet_at_node(forward_hops + 1, hop, false, fwd_packet, |_| None)
867 .context(format!("packet decoding failed at hop {hop}"))?;
868
869 match &fwd_packet {
870 HoprPacket::Final(incoming) => {
871 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
872 assert_eq!(pseudonym, incoming.sender, "invalid sender");
873 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
874 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
875 received_fwd_plain_text = incoming.plain_text.clone();
876 received_surbs.extend(incoming.surbs.clone());
877 }
878 HoprPacket::Forwarded(fwd) => {
879 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
880 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
881 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
882 }
883 HoprPacket::Outgoing { .. } => bail!("invalid packet state at hop {hop}"),
884 }
885 }
886
887 assert_eq!(received_fwd_plain_text.as_ref(), fwd_msg, "invalid plaintext");
888 assert_eq!(1, received_surbs.len(), "invalid number of surbs");
889 assert_eq!(
890 return_hops as u8 + 1,
891 received_surbs[0]
892 .1
893 .additional_data_receiver
894 .proof_of_relay_values()
895 .chain_length(),
896 "surb has invalid por chain length"
897 );
898
899 let re_msg = b"some testing reply message";
901 let mut re_packet = create_packet_from_surb(
902 forward_hops + 1,
903 received_surbs[0].0,
904 received_surbs[0].1.clone(),
905 &pseudonym,
906 re_msg,
907 )?;
908
909 let mut openers_fn = |p: &HoprSenderId| {
910 assert_eq!(p.pseudonym(), pseudonym);
911 let opener = openers.pop();
912 assert!(opener.as_ref().is_none_or(|(id, _)| id == &p.surb_id()));
913 opener.map(|(_, opener)| opener)
914 };
915
916 match &re_packet {
917 HoprPacket::Outgoing { .. } => {}
918 _ => bail!("invalid packet initial state"),
919 }
920
921 let mut received_re_plain_text = Box::default();
922 for hop in (0..=return_hops).rev() {
923 re_packet = process_packet_at_node(return_hops + 1, hop, true, re_packet, &mut openers_fn)
924 .context(format!("packet decoding failed at hop {hop}"))?;
925
926 match &re_packet {
927 HoprPacket::Final(incoming) => {
928 assert_eq!(hop, 0, "final packet must be at the last hop");
929 assert_eq!(pseudonym, incoming.sender, "invalid sender");
930 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
931 assert!(incoming.surbs.is_empty(), "must not receive surbs on reply");
932 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
933 received_re_plain_text = incoming.plain_text.clone();
934 }
935 HoprPacket::Forwarded(fwd) => {
936 assert_eq!(PEERS[hop + 1].1.public(), &fwd.previous_hop, "invalid previous hop");
937 assert_eq!(PEERS[hop - 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
938 assert_eq!(hop, fwd.path_pos as usize, "invalid path position");
939 }
940 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
941 }
942 }
943
944 assert_eq!(received_re_plain_text.as_ref(), re_msg, "invalid plaintext");
945 Ok(())
946 }
947
948 #[parameterized(
949 forward_hops = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 },
950 return_hops = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 }
951 )]
952 fn test_packet_surbs_only_and_reply_message(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
953 let pseudonym = SimplePseudonym::random();
954
955 let (mut fwd_packet, mut openers) = create_packet(forward_hops, pseudonym, vec![return_hops; 2], &[])?;
957
958 assert_eq!(2, openers.len(), "invalid number of openers");
959 match &fwd_packet {
960 HoprPacket::Outgoing { .. } => {}
961 _ => bail!("invalid packet initial state"),
962 }
963
964 let mut received_surbs = vec![];
965 for hop in 1..=forward_hops + 1 {
966 fwd_packet = process_packet_at_node(forward_hops + 1, hop, false, fwd_packet, |_| None)
967 .context(format!("packet decoding failed at hop {hop}"))?;
968
969 match &fwd_packet {
970 HoprPacket::Final(incoming) => {
971 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
972 assert!(
973 incoming.plain_text.is_empty(),
974 "must not receive plaintext on surbs only packet"
975 );
976 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
977 assert_eq!(2, incoming.surbs.len(), "invalid number of received surbs per packet");
978 assert_eq!(pseudonym, incoming.sender, "invalid sender");
979 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
980 received_surbs.extend(incoming.surbs.clone());
981 }
982 HoprPacket::Forwarded(fwd) => {
983 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
984 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
985 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
986 }
987 HoprPacket::Outgoing { .. } => bail!("invalid packet state at hop {hop}"),
988 }
989 }
990
991 assert_eq!(2, received_surbs.len(), "invalid number of surbs");
992 for recv_surb in &received_surbs {
993 assert_eq!(
994 return_hops as u8 + 1,
995 recv_surb
996 .1
997 .additional_data_receiver
998 .proof_of_relay_values()
999 .chain_length(),
1000 "surb has invalid por chain length"
1001 );
1002 }
1003
1004 let mut openers_fn = |p: &HoprSenderId| {
1005 assert_eq!(p.pseudonym(), pseudonym);
1006 let (id, opener) = openers.remove(0);
1007 assert_eq!(id, p.surb_id());
1008 Some(opener)
1009 };
1010
1011 for (i, recv_surb) in received_surbs.into_iter().enumerate() {
1013 let re_msg = format!("some testing reply message {i}");
1014 let mut re_packet = create_packet_from_surb(
1015 forward_hops + 1,
1016 recv_surb.0,
1017 recv_surb.1,
1018 &pseudonym,
1019 re_msg.as_bytes(),
1020 )?;
1021
1022 match &re_packet {
1023 HoprPacket::Outgoing { .. } => {}
1024 _ => bail!("invalid packet initial state in reply {i}"),
1025 }
1026
1027 let mut received_re_plain_text = Box::default();
1028 for hop in (0..=return_hops).rev() {
1029 re_packet = process_packet_at_node(return_hops + 1, hop, true, re_packet, &mut openers_fn)
1030 .context(format!("packet decoding failed at hop {hop} in reply {i}"))?;
1031
1032 match &re_packet {
1033 HoprPacket::Final(incoming) => {
1034 assert_eq!(hop, 0, "final packet must be at the last hop for reply {i}");
1035 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
1036 assert!(
1037 incoming.surbs.is_empty(),
1038 "must not receive surbs on reply for reply {i}"
1039 );
1040 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
1041 received_re_plain_text = incoming.plain_text.clone();
1042 }
1043 HoprPacket::Forwarded(fwd) => {
1044 assert_eq!(
1045 PEERS[hop + 1].1.public(),
1046 &fwd.previous_hop,
1047 "invalid previous hop in reply {i}"
1048 );
1049 assert_eq!(
1050 PEERS[hop - 1].1.public(),
1051 &fwd.outgoing.next_hop,
1052 "invalid next hop in reply {i}"
1053 );
1054 assert_eq!(hop, fwd.path_pos as usize, "invalid path position in reply {i}");
1055 }
1056 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop} in reply {i}"),
1057 }
1058 }
1059
1060 assert_eq!(
1061 received_re_plain_text.as_ref(),
1062 re_msg.as_bytes(),
1063 "invalid plaintext in reply {i}"
1064 );
1065 }
1066 Ok(())
1067 }
1068}