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