1use hopr_crypto_random::random_fill;
2use hopr_crypto_types::prelude::*;
3use hopr_primitive_types::prelude::*;
4
5use crate::derivation::derive_mac_key;
6use crate::prg::{PRGParameters, PRG};
7use crate::routing::ForwardedHeader::{FinalNode, RelayNode};
8use crate::shared_keys::{SharedSecret, SphinxSuite};
9
10const RELAYER_END_PREFIX: u8 = 0xff;
11
12const PATH_POSITION_LEN: usize = 1;
13
14const fn routing_information_length<S: SphinxSuite>(additional_data_relayer_len: usize) -> usize {
16 <S::P as Keypair>::Public::SIZE + PATH_POSITION_LEN + SimpleMac::SIZE + additional_data_relayer_len
17}
18
19pub const fn header_length<S: SphinxSuite>(
21 max_hops: usize,
22 additional_data_relayer_len: usize,
23 additional_data_last_hop_len: usize,
24) -> usize {
25 let last_hop = additional_data_last_hop_len + 1; last_hop + (max_hops - 1) * routing_information_length::<S>(additional_data_relayer_len)
27}
28
29fn generate_filler(
30 max_hops: usize,
31 routing_info_len: usize,
32 routing_info_last_hop_len: usize,
33 secrets: &[SharedSecret],
34) -> Box<[u8]> {
35 if secrets.len() < 2 {
36 return vec![].into_boxed_slice();
37 }
38
39 assert!(max_hops >= secrets.len(), "too few hops");
40 assert!(routing_info_len > 0, "invalid routing info length");
41
42 let header_len = routing_info_last_hop_len + (max_hops - 1) * routing_info_len;
43 let padding_len = (max_hops - secrets.len()) * routing_info_len;
44
45 let mut ret = vec![0u8; header_len - padding_len - routing_info_last_hop_len];
46
47 let mut length = routing_info_len;
48 let mut start = header_len;
49
50 for secret in secrets.iter().take(secrets.len() - 1) {
51 let prg = PRG::from_parameters(PRGParameters::new(secret));
52
53 let digest = prg.digest(start, header_len + routing_info_len);
54 xor_inplace(&mut ret[0..length], digest.as_ref());
55
56 length += routing_info_len;
57 start -= routing_info_len;
58 }
59
60 ret.into_boxed_slice()
61}
62
63pub struct RoutingInfo {
65 pub routing_information: Box<[u8]>,
66 pub mac: [u8; SimpleMac::SIZE],
67}
68
69impl Default for RoutingInfo {
70 fn default() -> Self {
71 Self {
72 routing_information: Box::default(),
73 mac: [0u8; SimpleMac::SIZE],
74 }
75 }
76}
77
78impl RoutingInfo {
79 pub fn new<S: SphinxSuite>(
88 max_hops: usize,
89 path: &[<S::P as Keypair>::Public],
90 secrets: &[SharedSecret],
91 additional_data_relayer_len: usize,
92 additional_data_relayer: &[&[u8]],
93 additional_data_last_hop: Option<&[u8]>,
94 ) -> Self {
95 assert!(
96 secrets.len() <= max_hops && !secrets.is_empty(),
97 "invalid number of secrets given"
98 );
99 assert!(
100 additional_data_relayer
101 .iter()
102 .all(|r| r.len() == additional_data_relayer_len),
103 "invalid relayer data length"
104 );
105 assert!(
106 additional_data_last_hop.is_none() || !additional_data_last_hop.unwrap().is_empty(),
107 "invalid additional data for last hop"
108 );
109
110 let routing_info_len = routing_information_length::<S>(additional_data_relayer_len);
111 let last_hop_len = additional_data_last_hop.map(|d| d.len()).unwrap_or(0) + 1; let header_len = header_length::<S>(max_hops, additional_data_relayer_len, last_hop_len - 1);
113
114 let extended_header_len = last_hop_len + max_hops * routing_info_len;
115
116 let mut extended_header = vec![0u8; extended_header_len];
117 let mut ret = RoutingInfo::default();
118
119 for idx in 0..secrets.len() {
120 let inverted_idx = secrets.len() - idx - 1;
121 let prg = PRG::from_parameters(PRGParameters::new(&secrets[inverted_idx]));
122
123 if idx == 0 {
124 extended_header[0] = RELAYER_END_PREFIX;
125
126 if let Some(data) = additional_data_last_hop {
127 extended_header[1..data.len()].copy_from_slice(data);
128 }
129
130 let padding_len = (max_hops - secrets.len()) * routing_info_len;
131 if padding_len > 0 {
132 random_fill(&mut extended_header[last_hop_len..padding_len]);
133 }
134
135 let key_stream = prg.digest(0, last_hop_len + padding_len);
136 xor_inplace(&mut extended_header[0..last_hop_len + padding_len], &key_stream);
137
138 if secrets.len() > 1 {
139 let filler = generate_filler(max_hops, routing_info_len, last_hop_len, secrets);
140 extended_header[last_hop_len + padding_len..last_hop_len + padding_len + filler.len()]
141 .copy_from_slice(&filler);
142 }
143 } else {
144 extended_header.copy_within(0..header_len, routing_info_len);
145
146 let pub_key_size = <S::P as Keypair>::Public::SIZE;
147
148 extended_header[0] = idx as u8;
151 extended_header[PATH_POSITION_LEN..PATH_POSITION_LEN + pub_key_size]
152 .copy_from_slice(path[inverted_idx + 1].as_ref());
153 extended_header[PATH_POSITION_LEN + pub_key_size..PATH_POSITION_LEN + pub_key_size + SimpleMac::SIZE]
154 .copy_from_slice(&ret.mac);
155 extended_header[PATH_POSITION_LEN + pub_key_size + SimpleMac::SIZE
156 ..PATH_POSITION_LEN + pub_key_size + SimpleMac::SIZE + additional_data_relayer[inverted_idx].len()]
157 .copy_from_slice(additional_data_relayer[inverted_idx]);
158
159 let key_stream = prg.digest(0, header_len);
160 xor_inplace(&mut extended_header, &key_stream);
161 }
162
163 let mut m = SimpleMac::new(&derive_mac_key(&secrets[inverted_idx]));
164 m.update(&extended_header[0..header_len]);
165 m.finalize_into(&mut ret.mac);
166 }
167
168 ret.routing_information = (&extended_header[0..header_len]).into();
169 ret
170 }
171}
172
173pub enum ForwardedHeader {
176 RelayNode {
178 header: Box<[u8]>,
180 mac: [u8; SimpleMac::SIZE],
182 path_pos: u8,
184 next_node: Box<[u8]>,
186 additional_info: Box<[u8]>,
188 },
189
190 FinalNode {
192 additional_data: Box<[u8]>,
194 },
195}
196
197pub fn forward_header<S: SphinxSuite>(
210 secret: &SecretKey,
211 header: &mut [u8],
212 mac: &[u8],
213 max_hops: usize,
214 additional_data_relayer_len: usize,
215 additional_data_last_hop_len: usize,
216) -> hopr_crypto_types::errors::Result<ForwardedHeader> {
217 assert_eq!(SimpleMac::SIZE, mac.len(), "invalid mac length");
218
219 let routing_info_len = routing_information_length::<S>(additional_data_relayer_len);
220 let last_hop_len = additional_data_last_hop_len + 1; let header_len = header_length::<S>(max_hops, additional_data_relayer_len, last_hop_len - 1);
222
223 assert_eq!(header_len, header.len(), "invalid pre-header length");
224
225 let mut computed_mac = SimpleMac::new(&derive_mac_key(secret));
226 computed_mac.update(header);
227 if !mac.eq(computed_mac.finalize().as_slice()) {
228 return Err(CryptoError::TagMismatch);
229 }
230
231 let prg = PRG::from_parameters(PRGParameters::new(secret));
233 let key_stream = prg.digest(0, header_len);
234 xor_inplace(header, &key_stream);
235
236 if header[0] != RELAYER_END_PREFIX {
237 let path_pos: u8 = header[0];
239
240 let pub_key_size = <S::P as Keypair>::Public::SIZE;
241
242 let next_node: Box<[u8]> = (&header[PATH_POSITION_LEN..PATH_POSITION_LEN + pub_key_size]).into();
244
245 let mac: [u8; SimpleMac::SIZE] = (&header
247 [PATH_POSITION_LEN + pub_key_size..PATH_POSITION_LEN + pub_key_size + SimpleMac::SIZE])
248 .try_into()
249 .unwrap();
250
251 let additional_info: Box<[u8]> = (&header[PATH_POSITION_LEN + pub_key_size + SimpleMac::SIZE
252 ..PATH_POSITION_LEN + pub_key_size + SimpleMac::SIZE + additional_data_relayer_len])
253 .into();
254
255 header.copy_within(routing_info_len.., 0);
256 let key_stream = prg.digest(header_len, header_len + routing_info_len);
257 header[header_len - routing_info_len..].copy_from_slice(&key_stream);
258
259 Ok(RelayNode {
260 header: (&header[..header_len]).into(),
261 mac,
262 path_pos,
263 next_node,
264 additional_info,
265 })
266 } else {
267 Ok(FinalNode {
268 additional_data: (&header[1..1 + additional_data_last_hop_len]).into(),
269 })
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use hopr_crypto_types::keypairs::OffchainKeypair;
277 use parameterized::parameterized;
278
279 #[parameterized(hops = { 3, 4 })]
280 fn test_filler_generate_verify(hops: usize) {
281 let per_hop = 3;
282 let last_hop = 5;
283 let max_hops = hops;
284
285 let secrets = (0..hops).map(|_| SharedSecret::random()).collect::<Vec<_>>();
286 let extended_header_len = per_hop * max_hops + last_hop;
287 let header_len = per_hop * (max_hops - 1) + last_hop;
288
289 let mut extended_header = vec![0u8; per_hop * max_hops + last_hop];
290
291 let filler = generate_filler(max_hops, per_hop, last_hop, &secrets);
292
293 extended_header[last_hop..last_hop + filler.len()].copy_from_slice(&filler);
294 extended_header.copy_within(0..header_len, per_hop);
295
296 for i in 0..hops - 1 {
297 let idx = secrets.len() - i - 2;
298 let mask = PRG::from_parameters(PRGParameters::new(&secrets[idx])).digest(0, extended_header_len);
299
300 xor_inplace(&mut extended_header, &mask);
301
302 assert!(
303 extended_header[header_len..].iter().all(|x| *x == 0),
304 "xor blinding must erase last bits"
305 );
306
307 extended_header.copy_within(0..header_len, per_hop);
308 }
309 }
310
311 #[test]
312 fn test_filler_edge_case() {
313 let per_hop = 23;
314 let last_hop = 31;
315 let hops = 1;
316 let max_hops = hops;
317
318 let secrets = (0..hops).map(|_| SharedSecret::random()).collect::<Vec<_>>();
319
320 let first_filler = generate_filler(max_hops, per_hop, last_hop, &secrets);
321 assert_eq!(0, first_filler.len());
322
323 let second_filler = generate_filler(0, per_hop, last_hop, &[]);
324 assert_eq!(0, second_filler.len());
325 }
326
327 fn generic_test_generate_routing_info_and_forward<S>(keypairs: Vec<S::P>) -> anyhow::Result<()>
328 where
329 S: SphinxSuite,
330 {
331 const MAX_HOPS: usize = 3;
332 let mut additional_data: Vec<&[u8]> = Vec::with_capacity(keypairs.len());
333 for _ in 0..keypairs.len() {
334 let e: &[u8] = &[];
335 additional_data.push(e);
336 }
337
338 let pub_keys = keypairs.iter().map(|kp| kp.public().clone()).collect::<Vec<_>>();
339 let shares = S::new_shared_keys(&pub_keys).unwrap();
340
341 let rinfo = RoutingInfo::new::<S>(MAX_HOPS, &pub_keys, &shares.secrets, 0, &additional_data, None);
342
343 let mut header: Vec<u8> = rinfo.routing_information.into();
344
345 let mut last_mac = [0u8; SimpleMac::SIZE];
346 last_mac.copy_from_slice(&rinfo.mac);
347
348 for (i, secret) in shares.secrets.iter().enumerate() {
349 let fwd = forward_header::<S>(secret, &mut header, &last_mac, MAX_HOPS, 0, 0)?;
350
351 match fwd {
352 ForwardedHeader::RelayNode {
353 mac,
354 next_node,
355 path_pos,
356 ..
357 } => {
358 last_mac.copy_from_slice(&mac);
359 assert!(i < shares.secrets.len() - 1, "cannot be a relay node");
360 assert_eq!(
361 path_pos,
362 (shares.secrets.len() - i - 1) as u8,
363 "invalid path position {path_pos}"
364 );
365 assert_eq!(
366 pub_keys[i + 1].as_ref(),
367 next_node.as_ref(),
368 "invalid public key of the next node"
369 );
370 }
371 ForwardedHeader::FinalNode { additional_data } => {
372 assert_eq!(shares.secrets.len() - 1, i, "cannot be a final node");
373 assert_eq!(0, additional_data.len(), "final node must not have any additional data");
374 }
375 }
376 }
377
378 Ok(())
379 }
380
381 #[cfg(feature = "ed25519")]
382 #[parameterized(amount = { 3, 2, 1 })]
383 fn test_ed25519_generate_routing_info_and_forward(amount: usize) -> anyhow::Result<()> {
384 generic_test_generate_routing_info_and_forward::<crate::ec_groups::Ed25519Suite>(
385 (0..amount).map(|_| OffchainKeypair::random()).collect(),
386 )
387 }
388
389 #[cfg(feature = "x25519")]
390 #[parameterized(amount = { 3, 2, 1 })]
391 fn test_x25519_generate_routing_info_and_forward(amount: usize) -> anyhow::Result<()> {
392 generic_test_generate_routing_info_and_forward::<crate::ec_groups::X25519Suite>(
393 (0..amount).map(|_| OffchainKeypair::random()).collect(),
394 )
395 }
396
397 #[cfg(feature = "secp256k1")]
398 #[parameterized(amount = { 3, 2, 1 })]
399 fn test_secp256k1_generate_routing_info_and_forward(amount: usize) -> anyhow::Result<()> {
400 generic_test_generate_routing_info_and_forward::<crate::ec_groups::Secp256k1Suite>(
401 (0..amount).map(|_| ChainKeypair::random()).collect(),
402 )
403 }
404}