1use std::fmt::Formatter;
2
3use hopr_protocol_pix::{EntryShareGenerator, TaggedEncryptedPartialSsaShare};
4use hopr_types::{
5 crypto::prelude::*,
6 internal::{
7 prelude::*,
8 routing::{HoprSenderId, HoprSurbId},
9 },
10 primitive::prelude::*,
11};
12#[cfg(feature = "rayon")]
13use hopr_utils::parallelize::cpu::rayon::prelude::*;
14
15use crate::{
16 HoprEncryptedPartialSsaShare, HoprPixSpec, HoprPseudonym, HoprReplyOpener, HoprSphinxHeaderSpec, HoprSphinxSuite,
17 HoprSurb, PAYLOAD_SIZE_INT,
18 errors::{
19 PacketError::{PacketConstructionError, PacketDecodingError},
20 Result,
21 },
22 por::{ProofOfRelayString, ProofOfRelayValues, derive_ack_key_share, generate_proof_of_relay, pre_verify},
23 sphinx::prelude::*,
24 types::{HoprPacketMessage, HoprPacketParts, PacketSignals, SurbReceiverInfo},
25};
26
27#[derive(Clone)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct PartialHoprPacket {
38 partial_packet: PartialPacket<HoprSphinxSuite, HoprSphinxHeaderSpec>,
39 surbs: Vec<HoprSurb>,
40 openers: Vec<HoprReplyOpener>,
41 ticket: Ticket,
42 next_hop: OffchainPublicKey,
43 ack_challenge: HalfKeyChallenge,
44 encrypted_pix_share: Option<TaggedEncryptedPartialSsaShare<HoprPixSpec>>,
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 pub first_relayer_solution: Option<HalfKey>,
61}
62
63impl PathKeyData {
64 fn new(path: &[OffchainPublicKey]) -> Result<Self> {
65 let shared_keys = HoprSphinxSuite::new_shared_keys(path)?;
66 let (por_strings, (por_values, first_relayer_solution)) = generate_proof_of_relay(&shared_keys.secrets)?;
67
68 Ok(Self {
69 shared_keys,
70 por_strings,
71 por_values,
72 first_relayer_solution: (path.len() > 1).then_some(first_relayer_solution),
74 })
75 }
76
77 fn iter_from_paths(paths: Vec<&[OffchainPublicKey]>) -> Result<impl Iterator<Item = Self> + use<>> {
81 #[cfg(not(feature = "rayon"))]
82 let paths = paths.into_iter();
83
84 #[cfg(feature = "rayon")]
85 let paths = paths.into_par_iter();
86
87 paths
88 .map(Self::new)
89 .collect::<Result<Vec<_>>>()
90 .map(|paths| paths.into_iter())
91 }
92}
93
94impl PartialHoprPacket {
95 pub fn new<
107 G: EntryShareGenerator<HoprPixSpec>,
108 M: ProtocolKeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
109 P: NonEmptyPath<OffchainPublicKey> + Send,
110 >(
111 pseudonym: &HoprPseudonym,
112 routing: PacketRouting<P>,
113 chain_keypair: &ChainKeypair,
114 ticket: TicketBuilder,
115 mapper: &M,
116 pix_share_gen: &G,
117 domain_separator: &Hash,
118 ) -> Result<Self> {
119 match routing {
120 PacketRouting::ForwardPath {
121 forward_path,
122 return_paths,
123 generation,
124 } => {
125 let mut key_data = PathKeyData::iter_from_paths(
127 std::iter::once(forward_path.hops())
128 .chain(return_paths.iter().map(|p| p.hops()))
129 .collect(),
130 )?;
131
132 let PathKeyData {
133 shared_keys,
134 por_strings,
135 por_values,
136 ..
137 } = key_data
138 .next()
139 .ok_or_else(|| PacketConstructionError("empty path".into()))?;
140
141 let receiver_data = HoprSenderId::new(pseudonym);
142
143 let (surbs, openers): (Vec<_>, Vec<_>) = key_data
151 .zip(return_paths)
152 .zip(receiver_data.into_sequence())
153 .map(|((key_data, rp), data)| {
154 create_surb_for_path((rp, key_data), data, mapper, pix_share_gen, generation)
155 })
156 .collect::<Result<Vec<_>>>()?
157 .into_iter()
158 .unzip();
159
160 let ticket = ticket
162 .eth_challenge(por_values.ticket_challenge())
163 .build_signed(chain_keypair, domain_separator)?
164 .leak();
165
166 Ok(Self {
167 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
168 MetaPacketRouting::ForwardPath {
169 shared_keys,
170 forward_path: &forward_path,
171 receiver_data: &receiver_data,
172 additional_data_relayer: &por_strings,
173 no_ack: false,
174 },
175 mapper,
176 )?,
177 surbs,
178 openers,
179 ticket,
180 next_hop: forward_path[0],
181 ack_challenge: por_values.acknowledgement_challenge(),
182 encrypted_pix_share: None,
183 })
184 }
185 PacketRouting::Surb(id, surb) => {
186 let surb_por_values = surb.additional_data_receiver.proof_of_relay_values();
187
188 let ticket = ticket
190 .eth_challenge(surb_por_values.ticket_challenge())
191 .build_signed(chain_keypair, domain_separator)?
192 .leak();
193
194 let partial_share = surb.additional_data_receiver.encrypted_partial_ssa_share();
196
197 Ok(Self {
198 ticket,
199 next_hop: mapper.map_id_to_public(&surb.first_relayer).ok_or_else(|| {
200 PacketConstructionError(format!(
201 "failed to map key id {} to public key",
202 surb.first_relayer.to_hex()
203 ))
204 })?,
205 ack_challenge: surb_por_values.acknowledgement_challenge(),
206 encrypted_pix_share: (!partial_share.is_empty())
207 .then(|| TaggedEncryptedPartialSsaShare::new(id.pseudonym(), &surb.sender_key, partial_share))
208 .transpose()?,
209 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
210 MetaPacketRouting::Surb(surb, &id),
211 mapper,
212 )?,
213 surbs: vec![],
214 openers: vec![],
215 })
216 }
217 PacketRouting::NoAck(destination) => {
218 let PathKeyData {
220 shared_keys,
221 por_strings,
222 por_values,
223 ..
224 } = PathKeyData::new(&[destination])?;
225
226 let ticket = ticket
228 .eth_challenge(por_values.ticket_challenge())
229 .build_signed(chain_keypair, domain_separator)?
230 .leak();
231
232 Ok(Self {
233 partial_packet: PartialPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec>::new(
234 MetaPacketRouting::ForwardPath {
235 shared_keys,
236 forward_path: &[destination],
237 receiver_data: &HoprSenderId::new(pseudonym),
238 additional_data_relayer: &por_strings,
239 no_ack: true, },
241 mapper,
242 )?,
243 ticket,
244 next_hop: destination,
245 ack_challenge: por_values.acknowledgement_challenge(),
246 surbs: vec![],
247 openers: vec![],
248 encrypted_pix_share: None,
249 })
250 }
251 }
252 }
253
254 pub fn into_hopr_packet<S: Into<PacketSignals>>(
259 self,
260 msg: &[u8],
261 signals: S,
262 ) -> Result<(HoprPacket, Vec<HoprReplyOpener>)> {
263 let msg = HoprPacketMessage::try_from(HoprPacketParts {
264 surbs: self.surbs,
265 payload: msg.into(),
266 signals: signals.into(),
267 })?;
268 Ok((
269 HoprPacket::Outgoing(
270 HoprOutgoingPacket {
271 packet: self.partial_packet.into_meta_packet(msg.into()),
272 ticket: self.ticket,
273 next_hop: self.next_hop,
274 ack_challenge: self.ack_challenge,
275 encrypted_pix_share: self.encrypted_pix_share,
276 }
277 .into(),
278 ),
279 self.openers,
280 ))
281 }
282}
283
284#[derive(Clone)]
286pub struct HoprIncomingPacket {
287 pub packet_tag: PacketTag,
289 pub ack_key: Option<HalfKey>,
294 pub previous_hop: OffchainPublicKey,
296 pub plain_text: Box<[u8]>,
298 pub sender: HoprPseudonym,
300 pub surbs: Vec<(HoprSurbId, HoprSurb)>,
302 pub signals: PacketSignals,
306}
307
308impl std::fmt::Debug for HoprIncomingPacket {
309 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
310 f.debug_struct("HoprIncomingPacket")
311 .field("packet_tag", &self.packet_tag)
312 .field("ack_key", &self.ack_key)
313 .field("previous_hop", &self.previous_hop)
314 .field("sender", &self.sender)
315 .field("signals", &self.signals)
316 .finish_non_exhaustive()
317 }
318}
319
320#[derive(Clone)]
322pub struct HoprOutgoingPacket {
323 pub packet: MetaPacket<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>,
325 pub ticket: Ticket,
327 pub next_hop: OffchainPublicKey,
329 pub ack_challenge: HalfKeyChallenge,
331 pub encrypted_pix_share: Option<TaggedEncryptedPartialSsaShare<HoprPixSpec>>,
336}
337
338impl std::fmt::Debug for HoprOutgoingPacket {
339 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
340 f.debug_struct("HoprOutgoingPacket")
341 .field("ticket", &self.ticket)
342 .field("next_hop", &self.next_hop)
343 .field("ack_challenge", &self.ack_challenge)
344 .field("encrypted_pix_share", &self.encrypted_pix_share)
345 .finish_non_exhaustive()
346 }
347}
348
349#[derive(Clone)]
351pub struct HoprForwardedPacket {
352 pub outgoing: HoprOutgoingPacket,
354 pub packet_tag: PacketTag,
356 pub ack_key: HalfKey,
358 pub previous_hop: OffchainPublicKey,
360 pub own_key: HalfKey,
362 pub next_challenge: EthereumChallenge,
364 pub path_pos: u8,
366}
367
368impl std::fmt::Debug for HoprForwardedPacket {
369 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370 f.debug_struct("HoprForwardedPacket")
371 .field("outgoing", &self.outgoing)
372 .field("packet_tag", &const_hex::encode(self.packet_tag))
373 .field("ack_key", &self.ack_key)
374 .field("previous_hop", &self.previous_hop)
375 .field("own_key", &self.own_key)
376 .field("next_challenge", &self.next_challenge)
377 .field("path_pos", &self.path_pos)
378 .finish_non_exhaustive()
379 }
380}
381
382#[derive(Clone, Debug, strum::EnumTryAs, strum::EnumIs, strum::IntoStaticStr, strum::Display)]
388pub enum HoprPacket {
389 #[strum(to_string = "Final")]
391 Final(Box<HoprIncomingPacket>),
392 #[strum(to_string = "Forwarded")]
394 Forwarded(Box<HoprForwardedPacket>),
395 #[strum(to_string = "Outgoing")]
397 Outgoing(Box<HoprOutgoingPacket>),
398}
399
400impl HoprPacket {
401 pub fn packet_tag(&self) -> Option<&PacketTag> {
403 match self {
404 HoprPacket::Final(packet) => Some(&packet.packet_tag),
405 HoprPacket::Forwarded(packet) => Some(&packet.packet_tag),
406 HoprPacket::Outgoing(_) => None,
407 }
408 }
409}
410
411#[derive(Clone)]
413pub enum PacketRouting<P: NonEmptyPath<OffchainPublicKey> = TransportPath> {
414 ForwardPath {
423 forward_path: P,
424 return_paths: Vec<P>,
425 generation: u8,
426 },
427 Surb(HoprSenderId, HoprSurb),
429 NoAck(OffchainPublicKey),
432}
433
434fn create_surb_for_path<
435 G: EntryShareGenerator<HoprPixSpec>,
436 M: ProtocolKeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
437 P: NonEmptyPath<OffchainPublicKey>,
438>(
439 return_path: (P, PathKeyData),
440 recv_data: HoprSenderId,
441 mapper: &M,
442 pix_share_gen: &G,
443 generation: u8,
444) -> Result<(HoprSurb, HoprReplyOpener)> {
445 let (
446 return_path,
447 PathKeyData {
448 shared_keys,
449 por_strings,
450 por_values,
451 first_relayer_solution,
452 },
453 ) = return_path;
454
455 debug_assert!(
458 (return_path.len() == 1 && first_relayer_solution.is_none())
459 || (return_path.len() > 1 && first_relayer_solution.is_some()),
460 "multi-hop return path must have a first relayer challenge solution"
461 );
462
463 tracing::debug!(
464 has_first_relayer_solution = first_relayer_solution.is_some(),
465 "prepared return-path PoR data"
466 );
467
468 let (mut surb, (surb_id, ro)) = create_surb::<HoprSphinxSuite, HoprSphinxHeaderSpec>(
469 shared_keys,
470 &return_path
471 .iter()
472 .map(|k| {
473 mapper
474 .map_key_to_id(k)
475 .ok_or_else(|| PacketConstructionError(format!("failed to map key {} to id", k.to_hex())))
476 })
477 .collect::<Result<Vec<_>>>()?,
478 &por_strings,
479 recv_data,
480 SurbReceiverInfo::new(por_values, HoprEncryptedPartialSsaShare::default(), generation),
481 )
482 .map(|(s, r)| (s, (recv_data.surb_id(), r)))?;
483
484 if let Some(first_solution) = first_relayer_solution
486 && let Some(enc_share) = pix_share_gen
487 .next_share(&recv_data.pseudonym(), &ro.sender_key)
488 .map_err(|e| PacketConstructionError(format!("failed to generate PIX share: {e}")))?
489 .map(|gen_share| gen_share.encrypt(&first_solution))
490 .transpose()?
491 {
492 surb.additional_data_receiver = SurbReceiverInfo::new(por_values, enc_share, generation);
495 }
496
497 Ok((surb, (surb_id, ro)))
498}
499
500const _: () = assert!(
503 HoprPacket::MAX_SURBS_IN_PACKET == 8,
504 "SURB size grew enough to reduce MAX_SURBS_IN_PACKET; the generation carrier must move to spare bits (e.g. \
505 ProofOfRelayValues chain_length high bits) instead of enlarging SurbReceiverInfo"
506);
507
508impl HoprPacket {
509 pub const MAX_SURBS_IN_PACKET: usize = HoprPacket::PAYLOAD_SIZE / HoprSurb::SIZE;
511 pub const PAYLOAD_SIZE: usize = PAYLOAD_SIZE_INT - HoprPacketMessage::HEADER_LEN;
515 pub const SIZE: usize =
517 MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN + Ticket::SIZE;
518
519 #[allow(clippy::too_many_arguments)] pub fn into_outgoing<
536 G: EntryShareGenerator<HoprPixSpec>,
537 M: ProtocolKeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
538 P: NonEmptyPath<OffchainPublicKey> + Send,
539 S: Into<PacketSignals>,
540 >(
541 msg: &[u8],
542 pseudonym: &HoprPseudonym,
543 routing: PacketRouting<P>,
544 chain_keypair: &ChainKeypair,
545 ticket: TicketBuilder,
546 mapper: &M,
547 domain_separator: &Hash,
548 pix_share_gen: &G,
549 signals: S,
550 ) -> Result<(Self, Vec<HoprReplyOpener>)> {
551 PartialHoprPacket::new(
552 pseudonym,
553 routing,
554 chain_keypair,
555 ticket,
556 mapper,
557 pix_share_gen,
558 domain_separator,
559 )?
560 .into_hopr_packet(msg, signals)
561 }
562
563 pub const fn max_surbs_with_message(msg_len: usize) -> usize {
566 HoprPacket::PAYLOAD_SIZE.saturating_sub(msg_len) / HoprSurb::SIZE
567 }
568
569 pub const fn max_message_with_surbs(num_surbs: usize) -> usize {
572 HoprPacket::PAYLOAD_SIZE.saturating_sub(num_surbs * HoprSurb::SIZE)
573 }
574
575 pub fn from_incoming<M, F>(
578 data: &[u8],
579 node_keypair: &OffchainKeypair,
580 previous_hop: OffchainPublicKey,
581 mapper: &M,
582 reply_openers: F,
583 ) -> Result<Self>
584 where
585 M: ProtocolKeyIdMapper<HoprSphinxSuite, HoprSphinxHeaderSpec>,
586 F: FnMut(&HoprSenderId) -> Option<ReplyOpener>,
587 {
588 if data.len() == Self::SIZE {
589 let (pre_packet, pre_ticket) =
590 data.split_at(MetaPacket::<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>::PACKET_LEN);
591
592 let mp: MetaPacket<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT> =
593 MetaPacket::try_from(pre_packet)?;
594
595 match mp.into_forwarded(node_keypair, mapper, reply_openers)? {
596 ForwardedMetaPacket::Relayed {
597 packet,
598 derived_secret,
599 additional_info,
600 packet_tag,
601 next_node,
602 path_pos,
603 ..
604 } => {
605 let ack_key = derive_ack_key_share(&derived_secret);
606
607 let ticket = Ticket::try_from(pre_ticket)?;
608 let verification_output = pre_verify(&derived_secret, &additional_info, &ticket.challenge)?;
609 Ok(Self::Forwarded(
610 HoprForwardedPacket {
611 outgoing: HoprOutgoingPacket {
612 packet,
613 ticket,
614 next_hop: next_node,
615 ack_challenge: verification_output.ack_challenge,
616 encrypted_pix_share: None,
617 },
618 packet_tag,
619 ack_key,
620 previous_hop,
621 path_pos,
622 own_key: verification_output.own_key,
623 next_challenge: verification_output.next_ticket_challenge,
624 }
625 .into(),
626 ))
627 }
628 ForwardedMetaPacket::Final {
629 packet_tag,
630 plain_text,
631 derived_secret,
632 receiver_data,
633 no_ack,
634 } => {
635 let HoprPacketParts {
637 surbs,
638 payload,
639 signals,
640 } = HoprPacketMessage::from(plain_text).try_into()?;
641 let should_acknowledge = !no_ack;
642 Ok(Self::Final(
643 HoprIncomingPacket {
644 packet_tag,
645 ack_key: should_acknowledge.then(|| derive_ack_key_share(&derived_secret)),
646 previous_hop,
647 plain_text: payload.into(),
648 surbs: receiver_data.into_sequence().map(|d| d.surb_id()).zip(surbs).collect(),
649 sender: receiver_data.pseudonym(),
650 signals,
651 }
652 .into(),
653 ))
654 }
655 }
656 } else {
657 Err(PacketDecodingError("packet has invalid size".into()))
658 }
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use anyhow::{Context, bail};
665 use bimap::BiHashMap;
666 use hex_literal::hex;
667 use hopr_protocol_pix::SsaGeneratorConfig;
668 use hopr_types::crypto_random::Randomizable;
669 use parameterized::parameterized;
670
671 use super::*;
672 use crate::types::PacketSignal;
673
674 lazy_static::lazy_static! {
675 static ref PEERS: [(ChainKeypair, OffchainKeypair); 5] = [
676 (hex!("a7c486ceccf5ab53bd428888ab1543dc2667abd2d5e80aae918da8d4b503a426"), hex!("5eb212d4d6aa5948c4f71574d45dad43afef6d330edb873fca69d0e1b197e906")),
677 (hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"), hex!("e995db483ada5174666c46bafbf3628005aca449c94ebdc0c9239c3f65d61ae0")),
678 (hex!("ca4bdfd54a8467b5283a0216288fdca7091122479ccf3cfb147dfa59d13f3486"), hex!("9dec751c00f49e50fceff7114823f726a0425a68a8dc6af0e4287badfea8f4a4")),
679 (hex!("e306ebfb0d01d0da0952c9a567d758093a80622c6cb55052bf5f1a6ebd8d7b5c"), hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed")),
680 (hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775"), hex!("e0bf93e9c916104da00b1850adc4608bd7e9087bbd3f805451f4556aa6b3fd6e")),
681 ].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")));
682
683 static ref MAPPER: SimpleBiMapper<HoprSphinxSuite, HoprSphinxHeaderSpec> = PEERS
684 .iter()
685 .enumerate()
686 .map(|(i, (_, k))| (KeyIdent::from(i as u32), *k.public()))
687 .collect::<BiHashMap<_, _>>()
688 .into();
689 }
690
691 fn forward(
692 mut packet: HoprPacket,
693 chain_keypair: &ChainKeypair,
694 next_ticket: TicketBuilder,
695 domain_separator: &Hash,
696 ) -> HoprPacket {
697 if let HoprPacket::Forwarded(fwd) = &mut packet {
698 fwd.outgoing.ticket = next_ticket
699 .eth_challenge(fwd.next_challenge)
700 .build_signed(chain_keypair, domain_separator)
701 .expect("ticket should create")
702 .leak();
703 }
704
705 packet
706 }
707
708 impl HoprPacket {
709 pub fn to_bytes(&self) -> Box<[u8]> {
710 let dummy_ticket = hex!(
711 "67f0ca18102feec505e5bfedcc25963e9c64a6f8a250adcad7d2830dd607585700000000000000000000000000000000000000000000000000000000000000003891bf6fd4a78e868fc7ad477c09b16fc70dd01ea67e18264d17e3d04f6d8576de2e6472b0072e510df6e9fa1dfcc2727cc7633edfeb9ec13860d9ead29bee71d68de3736c2f7a9f42de76ccd57a5f5847bc7349"
712 );
713 let (packet, ticket) = match self {
714 Self::Final(packet) => (packet.plain_text.clone(), dummy_ticket.as_ref().into()),
715 Self::Forwarded(fwd) => (
716 Vec::from(fwd.outgoing.packet.as_ref()).into_boxed_slice(),
717 fwd.outgoing.ticket.into_boxed(),
718 ),
719 Self::Outgoing(out) => (
720 Vec::from(out.packet.as_ref()).into_boxed_slice(),
721 out.ticket.into_boxed(),
722 ),
723 };
724
725 let mut ret = Vec::with_capacity(Self::SIZE);
726 ret.extend_from_slice(packet.as_ref());
727 ret.extend_from_slice(&ticket);
728 ret.into_boxed_slice()
729 }
730 }
731
732 fn mock_ticket(next_peer_channel_key: &PublicKey, path_len: usize) -> anyhow::Result<TicketBuilder> {
733 assert!(path_len > 0);
734 let price_per_packet: U256 = 10000000000000000u128.into();
735
736 if path_len > 1 {
737 Ok(TicketBuilder::default()
738 .counterparty(next_peer_channel_key.to_address())
739 .amount(price_per_packet.div_f64(1.0)? * U256::from(path_len as u64 - 1))
740 .index(1)
741 .win_prob(WinningProbability::ALWAYS)
742 .channel_epoch(1)
743 .eth_challenge(Default::default()))
744 } else {
745 Ok(TicketBuilder::zero_hop().counterparty(next_peer_channel_key.to_address()))
746 }
747 }
748
749 const FLAGS: PacketSignal = PacketSignal::OutOfSurbs;
750
751 fn create_packet(
752 forward_hops: usize,
753 pseudonym: HoprPseudonym,
754 return_hops: Vec<usize>,
755 msg: &[u8],
756 ) -> anyhow::Result<(HoprPacket, Vec<HoprReplyOpener>)> {
757 assert!((0..=3).contains(&forward_hops), "forward hops must be between 1 and 3");
758 assert!(
759 return_hops.iter().all(|h| (0..=3).contains(h)),
760 "return hops must be between 1 and 3"
761 );
762
763 let ticket = mock_ticket(PEERS[1].0.public(), forward_hops + 1)?;
764 let forward_path = TransportPath::new(PEERS[1..=forward_hops + 1].iter().map(|kp| *kp.1.public()))?;
765
766 let return_paths = return_hops
767 .into_iter()
768 .map(|h| TransportPath::new(PEERS[0..=h].iter().rev().map(|kp| *kp.1.public())))
769 .collect::<std::result::Result<Vec<_>, hopr_types::internal::errors::PathError>>()?;
770
771 let ssa_gen = hopr_protocol_pix::SsaShareGenerator::new(SsaGeneratorConfig::default());
772
773 Ok(HoprPacket::into_outgoing(
774 msg,
775 &pseudonym,
776 PacketRouting::ForwardPath {
777 forward_path,
778 return_paths,
779 generation: 0,
780 },
781 &PEERS[0].0,
782 ticket,
783 &*MAPPER,
784 &Hash::default(),
785 &ssa_gen,
786 FLAGS,
787 )?)
788 }
789
790 fn create_packet_from_surb(
791 sender_node: usize,
792 surb_id: HoprSurbId,
793 surb: HoprSurb,
794 hopr_pseudonym: &HoprPseudonym,
795 msg: &[u8],
796 ) -> anyhow::Result<HoprPacket> {
797 assert!((1..=4).contains(&sender_node), "sender_node must be between 1 and 4");
798
799 let ticket = mock_ticket(
800 PEERS[sender_node - 1].0.public(),
801 surb.additional_data_receiver.proof_of_relay_values().chain_length() as usize,
802 )?;
803
804 let ssa_gen = hopr_protocol_pix::SsaShareGenerator::new(SsaGeneratorConfig::default());
805
806 Ok(HoprPacket::into_outgoing(
807 msg,
808 hopr_pseudonym,
809 PacketRouting::<TransportPath>::Surb(HoprSenderId::from_pseudonym_and_id(hopr_pseudonym, surb_id), surb),
810 &PEERS[sender_node].0,
811 ticket,
812 &*MAPPER,
813 &Hash::default(),
814 &ssa_gen,
815 FLAGS,
816 )?
817 .0)
818 }
819
820 fn process_packet_at_node<F>(
821 path_len: usize,
822 node_pos: usize,
823 is_reply: bool,
824 packet: HoprPacket,
825 openers: F,
826 ) -> anyhow::Result<HoprPacket>
827 where
828 F: FnMut(&HoprSenderId) -> Option<ReplyOpener>,
829 {
830 assert!((0..=4).contains(&node_pos), "node position must be between 1 and 3");
831
832 let prev_hop = match (node_pos, is_reply) {
833 (1, false) => *PEERS[0].1.public(),
834 (_, false) => *PEERS[node_pos - 1].1.public(),
835 (3, true) => *PEERS[4].1.public(),
836 (_, true) => *PEERS[node_pos + 1].1.public(),
837 };
838
839 let packet = HoprPacket::from_incoming(&packet.to_bytes(), &PEERS[node_pos].1, prev_hop, &*MAPPER, openers)
840 .context(format!("deserialization failure at hop {node_pos}"))?;
841
842 match &packet {
843 HoprPacket::Final(_) => Ok(packet),
844 HoprPacket::Forwarded(_) => {
845 let next_hop = match (node_pos, is_reply) {
846 (3, false) => *PEERS[4].0.public(),
847 (_, false) => *PEERS[node_pos + 1].0.public(),
848 (1, true) => *PEERS[0].0.public(),
849 (_, true) => *PEERS[node_pos - 1].0.public(),
850 };
851
852 let next_ticket = mock_ticket(&next_hop, path_len)?;
853 Ok(forward(
854 packet.clone(),
855 &PEERS[node_pos].0,
856 next_ticket,
857 &Hash::default(),
858 ))
859 }
860 HoprPacket::Outgoing(_) => bail!("invalid packet state"),
861 }
862 }
863
864 #[parameterized(hops = { 0,1,2,3 })]
865 fn test_packet_forward_message_no_surb(hops: usize) -> anyhow::Result<()> {
866 let msg = b"some testing forward message";
867 let pseudonym = SimplePseudonym::random();
868 let (mut packet, opener) = create_packet(hops, pseudonym, vec![], msg)?;
869
870 assert!(opener.is_empty());
871 match &packet {
872 HoprPacket::Outgoing { .. } => {}
873 _ => bail!("invalid packet initial state"),
874 }
875
876 let mut actual_plain_text = Box::default();
877 for hop in 1..=hops + 1 {
878 packet = process_packet_at_node(hops + 1, hop, false, packet, |_| None)
879 .context(format!("packet decoding failed at hop {hop}"))?;
880
881 match &packet {
882 HoprPacket::Final(packet) => {
883 assert_eq!(hop - 1, hops, "final packet must be at the last hop");
884 assert!(packet.ack_key.is_some(), "must not be a no-ack packet");
885 assert_eq!(PacketSignals::from(FLAGS), packet.signals);
886 actual_plain_text = packet.plain_text.clone();
887 }
888 HoprPacket::Forwarded(fwd) => {
889 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
890 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
891 assert_eq!(hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
892 }
893 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
894 }
895 }
896
897 assert_eq!(actual_plain_text.as_ref(), msg, "invalid plaintext");
898 Ok(())
899 }
900
901 #[parameterized(forward_hops = { 0,1,2,3 }, return_hops = { 0, 1, 2, 3})]
902 fn test_packet_forward_message_with_surb(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
903 let msg = b"some testing forward message";
904 let pseudonym = SimplePseudonym::random();
905 let (mut packet, openers) = create_packet(forward_hops, pseudonym, vec![return_hops], msg)?;
906
907 assert_eq!(1, openers.len(), "invalid number of openers");
908 match &packet {
909 HoprPacket::Outgoing { .. } => {}
910 _ => bail!("invalid packet initial state"),
911 }
912
913 let mut received_plain_text = Box::default();
914 let mut received_surbs = vec![];
915 for hop in 1..=forward_hops + 1 {
916 packet = process_packet_at_node(forward_hops + 1, hop, false, packet, |_| None)
917 .context(format!("packet decoding failed at hop {hop}"))?;
918
919 match &packet {
920 HoprPacket::Final(packet) => {
921 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
922 assert_eq!(pseudonym, packet.sender, "invalid sender");
923 assert!(packet.ack_key.is_some(), "must not be a no-ack packet");
924 assert_eq!(PacketSignals::from(FLAGS), packet.signals);
925 received_plain_text = packet.plain_text.clone();
926 received_surbs.extend(packet.surbs.clone());
927 }
928 HoprPacket::Forwarded(fwd) => {
929 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
930 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
931 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
932 }
933 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
934 }
935 }
936
937 assert_eq!(received_plain_text.as_ref(), msg, "invalid plaintext");
938 assert_eq!(1, received_surbs.len(), "invalid number of surbs");
939 assert_eq!(
940 return_hops as u8 + 1,
941 received_surbs[0]
942 .1
943 .additional_data_receiver
944 .proof_of_relay_values()
945 .chain_length(),
946 "surb has invalid por chain length"
947 );
948
949 Ok(())
950 }
951
952 #[parameterized(
953 forward_hops = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 },
954 return_hops = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 }
955 )]
956 fn test_packet_forward_and_reply_message(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
957 let pseudonym = SimplePseudonym::random();
958
959 let fwd_msg = b"some testing forward message";
961 let (mut fwd_packet, mut openers) = create_packet(forward_hops, pseudonym, vec![return_hops], fwd_msg)?;
962
963 assert_eq!(1, openers.len(), "invalid number of openers");
964 match &fwd_packet {
965 HoprPacket::Outgoing { .. } => {}
966 _ => bail!("invalid packet initial state"),
967 }
968
969 let mut received_fwd_plain_text = Box::default();
970 let mut received_surbs = vec![];
971 for hop in 1..=forward_hops + 1 {
972 fwd_packet = process_packet_at_node(forward_hops + 1, hop, false, fwd_packet, |_| None)
973 .context(format!("packet decoding failed at hop {hop}"))?;
974
975 match &fwd_packet {
976 HoprPacket::Final(incoming) => {
977 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
978 assert_eq!(pseudonym, incoming.sender, "invalid sender");
979 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
980 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
981 received_fwd_plain_text = incoming.plain_text.clone();
982 received_surbs.extend(incoming.surbs.clone());
983 }
984 HoprPacket::Forwarded(fwd) => {
985 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
986 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
987 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
988 }
989 HoprPacket::Outgoing { .. } => bail!("invalid packet state at hop {hop}"),
990 }
991 }
992
993 assert_eq!(received_fwd_plain_text.as_ref(), fwd_msg, "invalid plaintext");
994 assert_eq!(1, received_surbs.len(), "invalid number of surbs");
995 assert_eq!(
996 return_hops as u8 + 1,
997 received_surbs[0]
998 .1
999 .additional_data_receiver
1000 .proof_of_relay_values()
1001 .chain_length(),
1002 "surb has invalid por chain length"
1003 );
1004
1005 let re_msg = b"some testing reply message";
1007 let mut re_packet = create_packet_from_surb(
1008 forward_hops + 1,
1009 received_surbs[0].0,
1010 received_surbs[0].1.clone(),
1011 &pseudonym,
1012 re_msg,
1013 )?;
1014
1015 let mut openers_fn = |p: &HoprSenderId| {
1016 assert_eq!(p.pseudonym(), pseudonym);
1017 let opener = openers.pop();
1018 assert!(opener.as_ref().is_none_or(|(id, _)| id == &p.surb_id()));
1019 opener.map(|(_, opener)| opener)
1020 };
1021
1022 match &re_packet {
1023 HoprPacket::Outgoing { .. } => {}
1024 _ => bail!("invalid packet initial state"),
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}"))?;
1031
1032 match &re_packet {
1033 HoprPacket::Final(incoming) => {
1034 assert_eq!(hop, 0, "final packet must be at the last hop");
1035 assert_eq!(pseudonym, incoming.sender, "invalid sender");
1036 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
1037 assert!(incoming.surbs.is_empty(), "must not receive surbs on reply");
1038 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
1039 received_re_plain_text = incoming.plain_text.clone();
1040 }
1041 HoprPacket::Forwarded(fwd) => {
1042 assert_eq!(PEERS[hop + 1].1.public(), &fwd.previous_hop, "invalid previous hop");
1043 assert_eq!(PEERS[hop - 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
1044 assert_eq!(hop, fwd.path_pos as usize, "invalid path position");
1045 }
1046 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop}"),
1047 }
1048 }
1049
1050 assert_eq!(received_re_plain_text.as_ref(), re_msg, "invalid plaintext");
1051 Ok(())
1052 }
1053
1054 #[parameterized(
1055 forward_hops = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 },
1056 return_hops = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 }
1057 )]
1058 fn test_packet_surbs_only_and_reply_message(forward_hops: usize, return_hops: usize) -> anyhow::Result<()> {
1059 let pseudonym = SimplePseudonym::random();
1060
1061 let (mut fwd_packet, mut openers) = create_packet(forward_hops, pseudonym, vec![return_hops; 2], &[])?;
1063
1064 assert_eq!(2, openers.len(), "invalid number of openers");
1065 match &fwd_packet {
1066 HoprPacket::Outgoing { .. } => {}
1067 _ => bail!("invalid packet initial state"),
1068 }
1069
1070 let mut received_surbs = vec![];
1071 for hop in 1..=forward_hops + 1 {
1072 fwd_packet = process_packet_at_node(forward_hops + 1, hop, false, fwd_packet, |_| None)
1073 .context(format!("packet decoding failed at hop {hop}"))?;
1074
1075 match &fwd_packet {
1076 HoprPacket::Final(incoming) => {
1077 assert_eq!(hop - 1, forward_hops, "final packet must be at the last hop");
1078 assert!(
1079 incoming.plain_text.is_empty(),
1080 "must not receive plaintext on surbs only packet"
1081 );
1082 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
1083 assert_eq!(2, incoming.surbs.len(), "invalid number of received surbs per packet");
1084 assert_eq!(pseudonym, incoming.sender, "invalid sender");
1085 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
1086 received_surbs.extend(incoming.surbs.clone());
1087 }
1088 HoprPacket::Forwarded(fwd) => {
1089 assert_eq!(PEERS[hop - 1].1.public(), &fwd.previous_hop, "invalid previous hop");
1090 assert_eq!(PEERS[hop + 1].1.public(), &fwd.outgoing.next_hop, "invalid next hop");
1091 assert_eq!(forward_hops + 1 - hop, fwd.path_pos as usize, "invalid path position");
1092 }
1093 HoprPacket::Outgoing { .. } => bail!("invalid packet state at hop {hop}"),
1094 }
1095 }
1096
1097 assert_eq!(2, received_surbs.len(), "invalid number of surbs");
1098 for recv_surb in &received_surbs {
1099 assert_eq!(
1100 return_hops as u8 + 1,
1101 recv_surb
1102 .1
1103 .additional_data_receiver
1104 .proof_of_relay_values()
1105 .chain_length(),
1106 "surb has invalid por chain length"
1107 );
1108 }
1109
1110 let mut openers_fn = |p: &HoprSenderId| {
1111 assert_eq!(p.pseudonym(), pseudonym);
1112 let (id, opener) = openers.remove(0);
1113 assert_eq!(id, p.surb_id());
1114 Some(opener)
1115 };
1116
1117 for (i, recv_surb) in received_surbs.into_iter().enumerate() {
1119 let re_msg = format!("some testing reply message {i}");
1120 let mut re_packet = create_packet_from_surb(
1121 forward_hops + 1,
1122 recv_surb.0,
1123 recv_surb.1,
1124 &pseudonym,
1125 re_msg.as_bytes(),
1126 )?;
1127
1128 match &re_packet {
1129 HoprPacket::Outgoing { .. } => {}
1130 _ => bail!("invalid packet initial state in reply {i}"),
1131 }
1132
1133 let mut received_re_plain_text = Box::default();
1134 for hop in (0..=return_hops).rev() {
1135 re_packet = process_packet_at_node(return_hops + 1, hop, true, re_packet, &mut openers_fn)
1136 .context(format!("packet decoding failed at hop {hop} in reply {i}"))?;
1137
1138 match &re_packet {
1139 HoprPacket::Final(incoming) => {
1140 assert_eq!(hop, 0, "final packet must be at the last hop for reply {i}");
1141 assert!(incoming.ack_key.is_some(), "must not be a no-ack packet");
1142 assert!(
1143 incoming.surbs.is_empty(),
1144 "must not receive surbs on reply for reply {i}"
1145 );
1146 assert_eq!(PacketSignals::from(FLAGS), incoming.signals);
1147 received_re_plain_text = incoming.plain_text.clone();
1148 }
1149 HoprPacket::Forwarded(fwd) => {
1150 assert_eq!(
1151 PEERS[hop + 1].1.public(),
1152 &fwd.previous_hop,
1153 "invalid previous hop in reply {i}"
1154 );
1155 assert_eq!(
1156 PEERS[hop - 1].1.public(),
1157 &fwd.outgoing.next_hop,
1158 "invalid next hop in reply {i}"
1159 );
1160 assert_eq!(hop, fwd.path_pos as usize, "invalid path position in reply {i}");
1161 }
1162 HoprPacket::Outgoing(_) => bail!("invalid packet state at hop {hop} in reply {i}"),
1163 }
1164 }
1165
1166 assert_eq!(
1167 received_re_plain_text.as_ref(),
1168 re_msg.as_bytes(),
1169 "invalid plaintext in reply {i}"
1170 );
1171 }
1172 Ok(())
1173 }
1174}