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