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