1use std::{
2 fmt::{Debug, Formatter},
3 marker::PhantomData,
4 num::NonZeroUsize,
5};
6
7use hopr_types::{
8 crypto::{
9 crypto_traits::{StreamCipher, StreamCipherSeek, UniversalHash},
10 prelude::*,
11 types::Pseudonym,
12 },
13 crypto_random::random_fill,
14 primitive::prelude::*,
15};
16use typenum::Unsigned;
17
18use super::{
19 derivation::{generate_key, generate_key_iv},
20 shared_keys::SharedSecret,
21};
22
23const SPHINX_HEADER_VERSION: u8 = 1;
25
26const HASH_KEY_PRG: &str = "HASH_KEY_PRG";
27
28const HASH_KEY_TAG: &str = "HASH_KEY_TAG";
29
30pub trait SphinxHeaderSpec {
32 const MAX_HOPS: NonZeroUsize;
34
35 type KeyId: BytesRepresentable + Clone;
37
38 type Pseudonym: Pseudonym;
40
41 type RelayerData: BytesRepresentable;
43
44 type PacketReceiverData: BytesRepresentable;
48
49 type SurbReceiverData: BytesRepresentable;
53
54 type PRG: crypto_traits::StreamCipher + crypto_traits::StreamCipherSeek + crypto_traits::KeyIvInit;
56
57 type UH: crypto_traits::UniversalHash + crypto_traits::KeyInit;
59
60 const RELAYER_DATA_SIZE: usize = Self::RelayerData::SIZE;
62
63 const SURB_RECEIVER_DATA_SIZE: usize = Self::SurbReceiverData::SIZE;
65
66 const RECEIVER_DATA_SIZE: usize = Self::PacketReceiverData::SIZE;
68
69 const KEY_ID_SIZE: NonZeroUsize = NonZeroUsize::new(Self::KeyId::SIZE).unwrap();
71
72 const TAG_SIZE: usize = <Self::UH as crypto_traits::BlockSizeUser>::BlockSize::USIZE;
74
75 const ROUTING_INFO_LEN: usize =
79 HeaderPrefix::SIZE + Self::KEY_ID_SIZE.get() + Self::TAG_SIZE + Self::RELAYER_DATA_SIZE;
80
81 const HEADER_LEN: usize =
85 HeaderPrefix::SIZE + Self::RECEIVER_DATA_SIZE + (Self::MAX_HOPS.get() - 1) * Self::ROUTING_INFO_LEN;
86
87 const EXT_HEADER_LEN: usize =
91 HeaderPrefix::SIZE + Self::RECEIVER_DATA_SIZE + Self::MAX_HOPS.get() * Self::ROUTING_INFO_LEN;
92
93 fn generate_filler(secrets: &[SharedSecret]) -> hopr_types::crypto::errors::Result<Box<[u8]>> {
94 if secrets.len() < 2 {
95 return Ok(vec![].into_boxed_slice());
96 }
97
98 if secrets.len() > Self::MAX_HOPS.into() {
99 return Err(CryptoError::InvalidInputValue("secrets.len"));
100 }
101
102 let padding_len = (Self::MAX_HOPS.get() - secrets.len()) * Self::ROUTING_INFO_LEN;
103
104 let mut ret = vec![0u8; Self::HEADER_LEN - padding_len - Self::Pseudonym::SIZE - 1];
105 let mut length = Self::ROUTING_INFO_LEN;
106 let mut start = Self::HEADER_LEN;
107
108 for secret in secrets.iter().take(secrets.len() - 1) {
109 let mut prg = Self::new_prg(secret)?;
110 prg.seek(start);
111 prg.apply_keystream(&mut ret[0..length]);
112
113 length += Self::ROUTING_INFO_LEN;
114 start -= Self::ROUTING_INFO_LEN;
115 }
116
117 Ok(ret.into_boxed_slice())
118 }
119
120 fn new_prg(secret: &SecretKey) -> hopr_types::crypto::errors::Result<Self::PRG> {
122 generate_key_iv(secret, HASH_KEY_PRG, None)
123 }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131struct HeaderPrefix(u8);
132
133impl HeaderPrefix {
134 pub const SIZE: usize = 1;
135
136 pub fn new(is_reply: bool, no_ack: bool, path_pos: u8) -> Result<Self, GeneralError> {
137 if path_pos > 7 {
139 return Err(GeneralError::ParseError("HeaderPrefixByte".into()));
140 }
141
142 let mut out = 0;
143 out |= (SPHINX_HEADER_VERSION & 0x07) << 5;
144 out |= (no_ack as u8) << 4;
145 out |= (is_reply as u8) << 3;
146 out |= path_pos & 0x07;
147 Ok(Self(out))
148 }
149
150 #[inline]
151 pub fn is_reply(&self) -> bool {
152 (self.0 & 0x08) != 0
153 }
154
155 #[inline]
156 pub fn is_no_ack(&self) -> bool {
157 (self.0 & 0x10) != 0
158 }
159
160 #[inline]
161 pub fn path_position(&self) -> u8 {
162 self.0 & 0x07
163 }
164
165 #[inline]
166 pub fn is_final_hop(&self) -> bool {
167 self.path_position() == 0
168 }
169}
170
171impl From<HeaderPrefix> for u8 {
172 fn from(value: HeaderPrefix) -> Self {
173 value.0
174 }
175}
176
177impl TryFrom<u8> for HeaderPrefix {
178 type Error = GeneralError;
179
180 fn try_from(value: u8) -> Result<Self, Self::Error> {
181 if (value & 0xe0) >> 5 == SPHINX_HEADER_VERSION {
182 Ok(Self(value))
183 } else {
184 Err(GeneralError::ParseError("invalid header version".into()))
185 }
186 }
187}
188
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191pub struct RoutingInfo<H: SphinxHeaderSpec>(Box<[u8]>, PhantomData<H>);
192
193impl<H: SphinxHeaderSpec> Debug for RoutingInfo<H> {
194 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
195 write!(f, "{}", self.to_hex())
196 }
197}
198
199impl<H: SphinxHeaderSpec> Clone for RoutingInfo<H> {
200 fn clone(&self) -> Self {
201 Self(self.0.clone(), PhantomData)
202 }
203}
204
205impl<H: SphinxHeaderSpec> PartialEq for RoutingInfo<H> {
206 fn eq(&self, other: &Self) -> bool {
207 self.0 == other.0
208 }
209}
210
211impl<H: SphinxHeaderSpec> Eq for RoutingInfo<H> {}
212
213impl<H: SphinxHeaderSpec> Default for RoutingInfo<H> {
214 fn default() -> Self {
215 Self(vec![0u8; Self::SIZE].into_boxed_slice(), PhantomData)
216 }
217}
218
219impl<H: SphinxHeaderSpec> AsRef<[u8]> for RoutingInfo<H> {
220 fn as_ref(&self) -> &[u8] {
221 &self.0
222 }
223}
224
225impl<'a, H: SphinxHeaderSpec> TryFrom<&'a [u8]> for RoutingInfo<H> {
226 type Error = GeneralError;
227
228 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
229 if value.len() == Self::SIZE {
230 Ok(Self(value.into(), PhantomData))
231 } else {
232 Err(GeneralError::ParseError("RoutingInfo".into()))
233 }
234 }
235}
236
237impl<H: SphinxHeaderSpec> BytesRepresentable for RoutingInfo<H> {
238 const SIZE: usize = H::HEADER_LEN + H::TAG_SIZE;
239}
240
241impl<H: SphinxHeaderSpec> RoutingInfo<H> {
242 pub fn new(
252 path: &[H::KeyId],
253 secrets: &[SharedSecret],
254 additional_data_relayer: &[H::RelayerData],
255 receiver_data: &H::PacketReceiverData,
256 is_reply: bool,
257 no_ack: bool,
258 ) -> hopr_types::crypto::errors::Result<Self> {
259 assert!(H::MAX_HOPS.get() <= 7, "maximum number of hops supported is 7");
260
261 if path.len() != secrets.len() {
262 return Err(CryptoError::InvalidParameterSize {
263 name: "path",
264 expected: secrets.len(),
265 });
266 }
267
268 if secrets.len() > H::MAX_HOPS.get() {
269 return Err(CryptoError::InvalidInputValue("secrets.len"));
270 }
271
272 let mut extended_header = vec![0u8; H::EXT_HEADER_LEN];
273 let mut ret = RoutingInfo::default();
274
275 for idx in 0..secrets.len() {
276 let inverted_idx = secrets.len() - idx - 1;
277 let prefix = HeaderPrefix::new(is_reply, no_ack, idx as u8)?;
278
279 let mut prg = H::new_prg(&secrets[inverted_idx])?;
280
281 if idx == 0 {
282 extended_header[0] = prefix.into();
284
285 extended_header[HeaderPrefix::SIZE..HeaderPrefix::SIZE + H::PacketReceiverData::SIZE]
287 .copy_from_slice(receiver_data.as_ref());
288
289 let padding_len = (H::MAX_HOPS.get() - secrets.len()) * H::ROUTING_INFO_LEN;
291 if padding_len > 0 {
292 random_fill(
293 &mut extended_header[HeaderPrefix::SIZE + H::PacketReceiverData::SIZE
294 ..HeaderPrefix::SIZE + H::PacketReceiverData::SIZE + padding_len],
295 );
296 }
297
298 prg.apply_keystream(
300 &mut extended_header[0..HeaderPrefix::SIZE + H::PacketReceiverData::SIZE + padding_len],
301 );
302
303 if secrets.len() > 1 {
304 let filler = H::generate_filler(secrets)?;
305 extended_header[HeaderPrefix::SIZE + H::PacketReceiverData::SIZE + padding_len
306 ..HeaderPrefix::SIZE + H::PacketReceiverData::SIZE + padding_len + filler.len()]
307 .copy_from_slice(&filler);
308 }
309 } else {
310 extended_header.copy_within(0..H::HEADER_LEN, H::ROUTING_INFO_LEN);
312
313 extended_header[0] = prefix.into();
316
317 let key_ident = path[inverted_idx + 1].as_ref();
319 if key_ident.len() != H::KEY_ID_SIZE.get() {
320 return Err(CryptoError::InvalidParameterSize {
321 name: "path[..]",
322 expected: H::KEY_ID_SIZE.into(),
323 });
324 }
325 extended_header[HeaderPrefix::SIZE..HeaderPrefix::SIZE + H::KEY_ID_SIZE.get()]
327 .copy_from_slice(key_ident);
328
329 extended_header[HeaderPrefix::SIZE + H::KEY_ID_SIZE.get()
331 ..HeaderPrefix::SIZE + H::KEY_ID_SIZE.get() + H::TAG_SIZE]
332 .copy_from_slice(ret.mac());
333
334 if H::RELAYER_DATA_SIZE > 0
336 && let Some(relayer_data) = additional_data_relayer.get(inverted_idx).map(|d| d.as_ref())
337 {
338 if relayer_data.len() != H::RELAYER_DATA_SIZE {
339 return Err(CryptoError::InvalidParameterSize {
340 name: "additional_data_relayer[..]",
341 expected: H::RELAYER_DATA_SIZE,
342 });
343 }
344
345 extended_header[HeaderPrefix::SIZE + H::KEY_ID_SIZE.get() + H::TAG_SIZE
346 ..HeaderPrefix::SIZE + H::KEY_ID_SIZE.get() + H::TAG_SIZE + H::RELAYER_DATA_SIZE]
347 .copy_from_slice(relayer_data);
348 }
349
350 prg.apply_keystream(&mut extended_header[0..H::HEADER_LEN]);
352 }
353
354 let mut uh: H::UH = generate_key(&secrets[inverted_idx], HASH_KEY_TAG, None)
355 .map_err(|_| CryptoError::InvalidInputValue("mac_key"))?;
356 uh.update_padded(&extended_header[0..H::HEADER_LEN]);
357 ret.mac_mut().copy_from_slice(&uh.finalize());
358 }
359
360 ret.routing_mut().copy_from_slice(&extended_header[0..H::HEADER_LEN]);
361 Ok(ret)
362 }
363
364 fn mac(&self) -> &[u8] {
365 &self.0[H::HEADER_LEN..H::HEADER_LEN + H::TAG_SIZE]
366 }
367
368 fn routing_mut(&mut self) -> &mut [u8] {
369 &mut self.0[0..H::HEADER_LEN]
370 }
371
372 fn mac_mut(&mut self) -> &mut [u8] {
373 &mut self.0[H::HEADER_LEN..H::HEADER_LEN + H::TAG_SIZE]
374 }
375}
376
377pub enum ForwardedHeader<H: SphinxHeaderSpec> {
380 Relayed {
382 next_header: RoutingInfo<H>,
384 path_pos: u8,
386 next_node: H::KeyId,
388 additional_info: H::RelayerData,
390 },
391
392 Final {
394 receiver_data: H::PacketReceiverData,
397 is_reply: bool,
400 no_ack: bool,
402 },
403}
404
405pub fn forward_header<H: SphinxHeaderSpec>(
416 secret: &SecretKey,
417 header: &mut [u8],
418) -> hopr_types::crypto::errors::Result<ForwardedHeader<H>> {
419 if header.len() != RoutingInfo::<H>::SIZE {
420 return Err(CryptoError::InvalidParameterSize {
421 name: "header",
422 expected: H::HEADER_LEN,
423 });
424 }
425
426 let mut uh: H::UH =
428 generate_key(secret, HASH_KEY_TAG, None).map_err(|_| CryptoError::InvalidInputValue("mac_key"))?;
429 uh.update_padded(&header[0..H::HEADER_LEN]);
430 uh.verify(header[H::HEADER_LEN..H::HEADER_LEN + H::TAG_SIZE].into())
431 .map_err(|_| CryptoError::TagMismatch)?;
432
433 let mut prg = H::new_prg(secret)?;
435 prg.apply_keystream(&mut header[0..H::HEADER_LEN]);
436
437 let prefix = HeaderPrefix::try_from(header[0])?;
438
439 if !prefix.is_final_hop() {
440 let next_node = (&header[HeaderPrefix::SIZE..HeaderPrefix::SIZE + H::KEY_ID_SIZE.get()])
442 .try_into()
443 .map_err(|_| CryptoError::InvalidInputValue("next_node"))?;
444
445 let mut next_header = RoutingInfo::<H>::default();
446
447 next_header.mac_mut().copy_from_slice(
449 &header[HeaderPrefix::SIZE + H::KEY_ID_SIZE.get()..HeaderPrefix::SIZE + H::KEY_ID_SIZE.get() + H::TAG_SIZE],
450 );
451
452 let additional_info = (&header[HeaderPrefix::SIZE + H::KEY_ID_SIZE.get() + H::TAG_SIZE
454 ..HeaderPrefix::SIZE + H::KEY_ID_SIZE.get() + H::TAG_SIZE + H::RELAYER_DATA_SIZE])
455 .try_into()
456 .map_err(|_| CryptoError::InvalidInputValue("additional_relayer_data"))?;
457
458 header.copy_within(H::ROUTING_INFO_LEN..H::HEADER_LEN, 0);
460
461 header[H::HEADER_LEN - H::ROUTING_INFO_LEN..H::HEADER_LEN].fill(0);
463 prg.seek(H::HEADER_LEN);
464 prg.apply_keystream(&mut header[H::HEADER_LEN - H::ROUTING_INFO_LEN..H::HEADER_LEN]);
465
466 next_header.routing_mut().copy_from_slice(&header[0..H::HEADER_LEN]);
467
468 Ok(ForwardedHeader::Relayed {
469 next_header,
470 path_pos: prefix.path_position(),
471 next_node,
472 additional_info,
473 })
474 } else {
475 Ok(ForwardedHeader::Final {
476 receiver_data: (&header[HeaderPrefix::SIZE..HeaderPrefix::SIZE + H::PacketReceiverData::SIZE])
477 .try_into()
478 .map_err(|_| CryptoError::InvalidInputValue("receiver_data"))?,
479 is_reply: prefix.is_reply(),
480 no_ack: prefix.is_no_ack(),
481 })
482 }
483}
484
485#[cfg(test)]
486pub(crate) mod tests {
487 use hopr_types::{
488 crypto::{crypto_traits::BlockSizeUser, keypairs::OffchainKeypair},
489 crypto_random::Randomizable,
490 };
491 use parameterized::parameterized;
492
493 use super::{
494 super::{
495 shared_keys::{Alpha, GroupElement, SphinxSuite},
496 tests::*,
497 },
498 *,
499 };
500
501 #[test]
502 fn test_filler_generate_verify() -> anyhow::Result<()> {
503 let per_hop = 3 + OffchainPublicKey::SIZE + <Poly1305 as BlockSizeUser>::BlockSize::USIZE + 1;
504 let last_hop = SimplePseudonym::SIZE;
505 let max_hops = 4;
506
507 let secrets = (0..max_hops).map(|_| SharedSecret::random()).collect::<Vec<_>>();
508 let extended_header_len = per_hop * max_hops + last_hop + 1;
509 let header_len = per_hop * (max_hops - 1) + last_hop + 1;
510
511 let mut extended_header = vec![0u8; extended_header_len];
512
513 let filler = TestSpec::<OffchainPublicKey, 4, 3>::generate_filler(&secrets)?;
514
515 extended_header[1 + last_hop..1 + last_hop + filler.len()].copy_from_slice(&filler);
516 extended_header.copy_within(0..header_len, per_hop);
517
518 for i in 0..max_hops - 1 {
519 let idx = secrets.len() - i - 2;
520
521 let mut prg = generate_key_iv::<ChaCha20, _>(&secrets[idx], HASH_KEY_PRG, None)?;
522 prg.apply_keystream(&mut extended_header);
523
524 let mut erased = extended_header.clone();
525 erased[header_len..].iter_mut().for_each(|x| *x = 0);
526 assert_eq!(erased, extended_header, "xor blinding must erase last bits {i}");
527
528 extended_header.copy_within(0..header_len, per_hop);
529 }
530
531 Ok(())
532 }
533
534 #[test]
535 fn test_filler_edge_case() -> anyhow::Result<()> {
536 let hops = 1;
537
538 let secrets = (0..hops).map(|_| SharedSecret::random()).collect::<Vec<_>>();
539
540 let first_filler = TestSpec::<OffchainPublicKey, 1, 0>::generate_filler(&secrets)?;
541 assert_eq!(0, first_filler.len());
542
543 Ok(())
544 }
545
546 fn generic_test_generate_routing_info_and_forward<S>(keypairs: Vec<S::P>, reply: bool) -> anyhow::Result<()>
547 where
548 S: SphinxSuite,
549 for<'a> &'a Alpha<<S::G as GroupElement<S::E>>::AlphaLen>: From<&'a <S::P as Keypair>::Public>,
550 {
551 let pub_keys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
552 let shares = S::new_shared_keys(&pub_keys)?;
553 let pseudonym = SimplePseudonym::random();
554 let no_ack_flag = true;
555
556 let mut rinfo = RoutingInfo::<TestSpec<<S::P as Keypair>::Public, 3, 0>>::new(
557 &pub_keys,
558 &shares.secrets,
559 &[],
560 &pseudonym,
561 reply,
562 no_ack_flag,
563 )?;
564
565 for (i, secret) in shares.secrets.iter().enumerate() {
566 let fwd = forward_header::<TestSpec<<S::P as Keypair>::Public, 3, 0>>(secret, &mut rinfo.0)?;
567
568 match fwd {
569 ForwardedHeader::Relayed {
570 next_header,
571 next_node,
572 path_pos,
573 ..
574 } => {
575 rinfo = next_header;
576 assert!(i < shares.secrets.len() - 1, "cannot be a relay node");
577 assert_eq!(
578 path_pos,
579 (shares.secrets.len() - i - 1) as u8,
580 "invalid path position {path_pos}"
581 );
582 assert_eq!(
583 pub_keys[i + 1].as_ref(),
584 next_node.as_ref(),
585 "invalid public key of the next node"
586 );
587 }
588 ForwardedHeader::Final {
589 receiver_data,
590 is_reply,
591 no_ack,
592 } => {
593 assert_eq!(shares.secrets.len() - 1, i, "cannot be a final node");
594 assert_eq!(pseudonym, receiver_data, "invalid pseudonym");
595 assert_eq!(is_reply, reply, "invalid reply flag");
596 assert_eq!(no_ack, no_ack_flag, "invalid no_ack flag");
597 }
598 }
599 }
600
601 Ok(())
602 }
603
604 #[cfg(feature = "ed25519")]
605 #[parameterized(amount = { 3, 2, 1, 3, 2, 1 }, reply = { true, true, true, false, false, false })]
606 fn test_ed25519_generate_routing_info_and_forward(amount: usize, reply: bool) -> anyhow::Result<()> {
607 generic_test_generate_routing_info_and_forward::<crate::sphinx::ec_groups::Ed25519Suite>(
608 (0..amount).map(|_| OffchainKeypair::random()).collect(),
609 reply,
610 )
611 }
612
613 #[cfg(feature = "x25519")]
614 #[parameterized(amount = { 3, 2, 1, 3, 2, 1 }, reply = { true, true, true, false, false, false })]
615 fn test_x25519_generate_routing_info_and_forward(amount: usize, reply: bool) -> anyhow::Result<()> {
616 generic_test_generate_routing_info_and_forward::<crate::sphinx::ec_groups::X25519Suite>(
617 (0..amount).map(|_| OffchainKeypair::random()).collect(),
618 reply,
619 )
620 }
621
622 #[cfg(feature = "secp256k1")]
623 #[parameterized(amount = { 3, 2, 1, 3, 2, 1 }, reply = { true, true, true, false, false, false })]
624 fn test_secp256k1_generate_routing_info_and_forward(amount: usize, reply: bool) -> anyhow::Result<()> {
625 generic_test_generate_routing_info_and_forward::<crate::sphinx::ec_groups::Secp256k1Suite>(
626 (0..amount).map(|_| ChainKeypair::random()).collect(),
627 reply,
628 )
629 }
630}