1use std::{fmt::Debug, marker::PhantomData};
2
3use cryptraits::{
4 convert::{FromBytes, Len, ToVec},
5 hash::Hash,
6 hmac::Hmac,
7 key::{Blind, SecretKey},
8 key_exchange::DiffieHellman,
9 stream_cipher::StreamCipher,
10};
11use zeroize::Zeroize;
12
13use crate::{
14 crypto::{
15 compute_blinding_factor, compute_mac, generate_cipher_stream, generate_encryption_key,
16 generate_padding, generate_shared_secrets, xor,
17 },
18 Address, SfynxError, ENCRYPTION, HASH,
19};
20
21#[derive(Clone, Zeroize)]
22pub struct Header<A, H, SC, ESK, HASH>
23where
24 A: Address,
25 H: Hmac,
26 SC: StreamCipher,
27 ESK: SecretKey + DiffieHellman,
28 HASH: Hash,
29{
30 max_relays: u8,
32
33 pub routing_info: Vec<u8>,
35
36 pub routing_info_mac: Vec<u8>,
38
39 pub public_key: <ESK as SecretKey>::PK,
40
41 #[zeroize(skip)]
42 _a: PhantomData<A>,
43
44 #[zeroize(skip)]
45 _h: PhantomData<H>,
46
47 #[zeroize(skip)]
48 _sc: PhantomData<SC>,
49
50 #[zeroize(skip)]
51 _hash: PhantomData<HASH>,
52}
53
54impl<A, H, SC, ESK, HASH> Debug for Header<A, H, SC, ESK, HASH>
55where
56 A: Address,
57 H: Hmac,
58 SC: StreamCipher,
59 ESK: SecretKey + DiffieHellman,
60 HASH: Hash,
61{
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("Header")
64 .field("routing_info", &self.routing_info)
65 .field("routing_info_mac", &self.routing_info_mac)
66 .field("public_key", &self.public_key)
67 .finish()
68 }
69}
70
71impl<A, H, SC, ESK, HASH> Drop for Header<A, H, SC, ESK, HASH>
72where
73 A: Address,
74 H: Hmac,
75 SC: StreamCipher,
76 ESK: SecretKey + DiffieHellman,
77 HASH: Hash,
78{
79 fn drop(&mut self) {
80 self.zeroize()
81 }
82}
83
84impl<A, H, SC, ESK, HASH> Header<A, H, SC, ESK, HASH>
85where
86 A: Address,
87 H: Hmac + Len,
88 SC: StreamCipher,
89 ESK: SecretKey + DiffieHellman<PK = <ESK as SecretKey>::PK> + Blind + ToVec,
90 <ESK as DiffieHellman>::SSK: ToVec,
91 <ESK as SecretKey>::PK: Blind + ToVec + FromBytes,
92 HASH: Hash,
93{
94 #[allow(clippy::type_complexity)]
95 pub fn new(
96 max_relays: u8,
97 routing_info: &[A],
98 dest: impl Address,
99 session_key: ESK,
100 circuit_pub_keys: Vec<<ESK as DiffieHellman>::PK>,
101 ) -> Result<
102 (
103 Vec<<ESK as DiffieHellman>::SSK>,
104 Header<A, H, SC, ESK, HASH>,
105 ),
106 SfynxError,
107 > {
108 let shared_secrets =
109 generate_shared_secrets::<ESK, HASH>(&circuit_pub_keys, session_key.clone())?;
110
111 Self::with_shared_secrets(max_relays, routing_info, dest, session_key, &shared_secrets)
112 }
113
114 #[allow(clippy::type_complexity)]
115 pub fn with_shared_secrets(
116 max_relays: u8,
117 routing_info: &[A],
118 dest: impl Address,
119 session_key: ESK,
120 shared_secrets: &[<ESK as DiffieHellman>::SSK],
121 ) -> Result<
122 (
123 Vec<<ESK as DiffieHellman>::SSK>,
124 Header<A, H, SC, ESK, HASH>,
125 ),
126 SfynxError,
127 > {
128 Self::validate_header_input(max_relays, routing_info)?;
129
130 let relay_data_size: usize = A::LEN + H::LEN;
131 let routing_info_size: usize = max_relays as usize * relay_data_size;
132 let stream_size = routing_info_size + relay_data_size;
133
134 let padding =
135 generate_padding::<H, SC>(A::LEN, max_relays.into(), shared_secrets, &[0; 12])
136 .map_err(|e| SfynxError::StreamCipherError(format!("{e:?}")))?;
137
138 let mut routing_info_bytes = vec![0; routing_info_size];
139
140 routing_info_bytes[routing_info_size - padding.len()..].copy_from_slice(&padding);
141
142 let mut dest = dest.to_vec();
143 let mut routing_info_mac = vec![0; H::LEN];
144
145 for (addr, shared_secret) in routing_info.iter().zip(shared_secrets).rev() {
146 let enc_key = generate_encryption_key::<H>(&shared_secret.to_vec(), ENCRYPTION);
147 let mac_key = generate_encryption_key::<H>(&shared_secret.to_vec(), HASH);
148
149 if Some(addr) != routing_info.last() {
150 routing_info_bytes.rotate_right(relay_data_size);
151 }
152
153 routing_info_bytes[..A::LEN].copy_from_slice(&dest);
154 routing_info_bytes[A::LEN..relay_data_size].copy_from_slice(&routing_info_mac);
155
156 let cipher = generate_cipher_stream::<SC>(&enc_key, &[0; 12], stream_size)
157 .map_err(|e| SfynxError::StreamCipherError(format!("{e:?}")))?;
158
159 xor(&mut routing_info_bytes, &cipher[..routing_info_size]);
160
161 if Some(addr) == routing_info.last() {
162 let len = routing_info_bytes.len() - padding.len();
163 routing_info_bytes[len..].copy_from_slice(&padding);
164 }
165
166 routing_info_mac = compute_mac::<H>(&mac_key, &routing_info_bytes);
167
168 dest = addr.to_vec();
169 }
170
171 Ok((
172 shared_secrets.to_vec(),
173 Header {
174 max_relays,
175 routing_info: routing_info_bytes.to_vec(),
176 routing_info_mac,
177 public_key: session_key.to_public(),
178 _a: Default::default(),
179 _h: Default::default(),
180 _sc: Default::default(),
181 _hash: Default::default(),
182 },
183 ))
184 }
185
186 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, SfynxError> {
187 let max_relays = bytes.as_ref()[0] as usize;
188 let bytes = &bytes.as_ref()[1..];
189
190 let routing_info = Vec::from(&bytes[..max_relays * (A::LEN + H::LEN)]);
191 let routing_info_mac = Vec::from(
192 &bytes[max_relays * (A::LEN + H::LEN)..max_relays * (A::LEN + H::LEN) + H::LEN],
193 );
194 let public_key =
195 <ESK as SecretKey>::PK::from_bytes(&bytes[max_relays * (A::LEN + H::LEN) + H::LEN..])
196 .map_err(|e| SfynxError::KeyPairError(format!("{e:?}")))?;
197
198 let max_relays = max_relays as u8;
199
200 Ok(Self {
201 max_relays,
202 routing_info,
203 routing_info_mac,
204 public_key,
205 _a: Default::default(),
206 _h: Default::default(),
207 _sc: Default::default(),
208 _hash: Default::default(),
209 })
210 }
211
212 pub fn to_vec(&self) -> Result<Vec<u8>, SfynxError> {
213 let mut bytes = Vec::new();
214 bytes.extend(&[self.max_relays]);
215 bytes.extend(&self.routing_info);
216 bytes.extend(&self.routing_info_mac);
217 bytes.extend(&self.public_key.to_vec());
218
219 Ok(bytes)
220 }
221
222 fn validate_header_input(max_relays: u8, routing_info: &[A]) -> Result<(), SfynxError>
224 where
225 A: Address,
226 {
227 if routing_info.len() > max_relays as usize {
228 Err(SfynxError::WrongRoutingInfoLength)
229 } else {
230 Ok(())
231 }
232 }
233
234 pub fn peel(
236 &self,
237 shared_secret: &<ESK as DiffieHellman>::SSK,
238 ) -> Result<(A, Self), SfynxError> {
239 let relay_data_size: usize = A::LEN + H::LEN;
240 let routing_info_size: usize = self.max_relays as usize * relay_data_size;
241 let stream_size = routing_info_size + relay_data_size;
242
243 let enc_key = generate_encryption_key::<H>(&shared_secret.to_vec(), ENCRYPTION);
244 let mac_key = generate_encryption_key::<H>(&shared_secret.to_vec(), HASH);
245
246 let routing_info_mac = compute_mac::<H>(&mac_key, &self.routing_info.to_vec());
247
248 if self.routing_info_mac != routing_info_mac {
249 return Err(SfynxError::InvalidMac);
250 }
251
252 let mut routing_info = self.routing_info.clone();
253 routing_info.extend(vec![0; H::LEN + A::LEN]);
254
255 let cipher = generate_cipher_stream::<SC>(&enc_key.to_vec(), &[0; 12], stream_size)
256 .map_err(|e| SfynxError::StreamCipherError(format!("{e:?}")))?;
257
258 xor(&mut routing_info[..stream_size], &cipher);
259
260 let next_addr = A::from_bytes(&routing_info[..A::LEN]);
261 let next_routing_info_mac = Vec::from(&routing_info[A::LEN..relay_data_size]);
262 let next_routing_info = Vec::from(&routing_info[relay_data_size..]);
263
264 let blinding_factor = compute_blinding_factor::<ESK, HASH>(&self.public_key, shared_secret);
265
266 let new_public_key = self
267 .public_key
268 .to_blind(&blinding_factor)
269 .map_err(|e| SfynxError::KeyPairError(format!("{e:?}")))?;
270
271 let next_header = Self {
272 max_relays: self.max_relays,
273 routing_info: next_routing_info,
274 routing_info_mac: next_routing_info_mac,
275 public_key: new_public_key,
276 _a: Default::default(),
277 _h: Default::default(),
278 _sc: Default::default(),
279 _hash: Default::default(),
280 };
281
282 Ok((next_addr, next_header))
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use cryptimitives::{hash::sha256, hmac, key::x25519_ristretto, stream_cipher::chacha20};
289 use cryptraits::{
290 convert::{Len, ToVec},
291 key::{Generate, SecretKey},
292 };
293
294 use crate::{crypto::generate_shared_secrets, header::Header, Address};
295
296 #[derive(Debug, PartialEq)]
297 struct TestAddress(String);
298
299 impl Address for TestAddress {
300 fn from_bytes(bytes: &[u8]) -> Self {
301 Self(String::from_utf8(bytes.to_vec()).unwrap())
302 }
303
304 fn to_vec(&self) -> Vec<u8> {
305 Vec::from(self.0.as_bytes())
306 }
307 }
308
309 impl Len for TestAddress {
310 const LEN: usize = 46;
311 }
312
313 #[test]
314 fn test_new_header() {
315 let num_relays = 4;
316 let dest = TestAddress(String::from(
317 "QmZrXVN6xNkXYqFharGfjG6CjdE3X85werKm8AyMdqsQKS",
318 ));
319
320 let relay_addrs = vec![
321 TestAddress(String::from(
322 "/ip4/127.0.0.1/udp/1234#0000000000000000000000",
323 )),
324 TestAddress(String::from(
325 "QmSFXZRzh6ZdpWXXQQ2mkYtx3ns39ZPtWgQJ7sSqStiHZH",
326 )),
327 TestAddress(String::from(
328 "/ip6/2607:f8b0:4003:c00::6a/udp/5678#000000000",
329 )),
330 TestAddress(String::from(
331 "/ip4/198.162.0.2/tcp/4321#00000000000000000000",
332 )),
333 ];
334
335 let mut circuit_keypairs = Vec::new();
336
337 for _ in 0..num_relays {
338 circuit_keypairs.push(x25519_ristretto::EphemeralSecretKey::generate());
339 }
340
341 let session_key = x25519_ristretto::EphemeralSecretKey::generate();
342
343 let shared_secrets =
344 generate_shared_secrets::<x25519_ristretto::EphemeralSecretKey, sha256::Hash>(
345 &circuit_keypairs
346 .iter()
347 .map(|k| k.to_public())
348 .collect::<Vec<x25519_ristretto::EphemeralPublicKey>>(),
349 session_key.clone(),
350 )
351 .unwrap();
352
353 let (_, header) = Header::<
354 TestAddress,
355 hmac::sha256::Hmac,
356 chacha20::StreamCipher,
357 x25519_ristretto::EphemeralSecretKey,
358 sha256::Hash,
359 >::with_shared_secrets(
360 num_relays, &relay_addrs, dest, session_key, &shared_secrets
361 )
362 .unwrap();
363
364 let mut count = 0;
366
367 for i in header.routing_info.iter().rev() {
368 if *i != 0 {
369 break;
370 }
371
372 count += 1;
373 }
374
375 assert!(
376 count <= 2,
377 "Header is revealing number of relays. Suffixed 0s count: {}",
378 count
379 );
380 }
381
382 #[test]
383 fn test_return_shared_secrets() {
384 let num_relays = 4;
385 let dest = TestAddress(String::from(
386 "QmZrXVN6xNkXYqFharGfjG6CjdE3X85werKm8AyMdqsQKS",
387 ));
388
389 let routing_info = vec![
390 TestAddress(String::from(
391 "/ip4/127.0.0.1/udp/1234#0000000000000000000000",
392 )),
393 TestAddress(String::from(
394 "QmSFXZRzh6ZdpWXXQQ2mkYtx3ns39ZPtWgQJ7sSqStiHZH",
395 )),
396 TestAddress(String::from(
397 "/ip6/2607:f8b0:4003:c00::6a/udp/5678#000000000",
398 )),
399 TestAddress(String::from(
400 "/ip4/198.162.0.2/tcp/4321#00000000000000000000",
401 )),
402 ];
403
404 let mut circuit_keypairs = Vec::new();
405
406 for _ in 0..num_relays {
407 circuit_keypairs.push(x25519_ristretto::EphemeralSecretKey::generate());
408 }
409
410 let session_key = x25519_ristretto::EphemeralSecretKey::generate();
411
412 let shared_secrets =
413 generate_shared_secrets::<x25519_ristretto::EphemeralSecretKey, sha256::Hash>(
414 &circuit_keypairs
415 .iter()
416 .map(|k| k.to_public())
417 .collect::<Vec<x25519_ristretto::EphemeralPublicKey>>(),
418 session_key.clone(),
419 )
420 .unwrap();
421
422 let (secrets, _) = Header::<
423 TestAddress,
424 hmac::sha256::Hmac,
425 chacha20::StreamCipher,
426 x25519_ristretto::EphemeralSecretKey,
427 sha256::Hash,
428 >::with_shared_secrets(
429 num_relays,
430 &routing_info,
431 dest,
432 session_key,
433 &shared_secrets,
434 )
435 .unwrap();
436
437 assert_eq!(
438 secrets.iter().map(|s| s.to_vec()).collect::<Vec<Vec<u8>>>(),
439 shared_secrets
440 .iter()
441 .map(|s| s.to_vec())
442 .collect::<Vec<Vec<u8>>>()
443 );
444 }
445
446 #[test]
447 fn test_header_from_bytes() {
448 let num_relays = 4;
449 let dest = TestAddress(String::from(
450 "QmZrXVN6xNkXYqFharGfjG6CjdE3X85werKm8AyMdqsQKS",
451 ));
452
453 let routing_info = vec![
454 TestAddress(String::from(
455 "/ip4/127.0.0.1/udp/1234#0000000000000000000000",
456 )),
457 TestAddress(String::from(
458 "QmSFXZRzh6ZdpWXXQQ2mkYtx3ns39ZPtWgQJ7sSqStiHZH",
459 )),
460 TestAddress(String::from(
461 "/ip6/2607:f8b0:4003:c00::6a/udp/5678#000000000",
462 )),
463 TestAddress(String::from(
464 "/ip4/198.162.0.2/tcp/4321#00000000000000000000",
465 )),
466 ];
467
468 let mut circuit_keypairs = Vec::new();
469
470 for _ in 0..num_relays {
471 circuit_keypairs.push(x25519_ristretto::EphemeralSecretKey::generate());
472 }
473
474 let session_key = x25519_ristretto::EphemeralSecretKey::generate();
475
476 let shared_secrets =
477 generate_shared_secrets::<x25519_ristretto::EphemeralSecretKey, sha256::Hash>(
478 &circuit_keypairs
479 .iter()
480 .map(|k| k.to_public())
481 .collect::<Vec<x25519_ristretto::EphemeralPublicKey>>(),
482 session_key.clone(),
483 )
484 .unwrap();
485
486 let (_, header) = Header::<
487 TestAddress,
488 hmac::sha256::Hmac,
489 chacha20::StreamCipher,
490 x25519_ristretto::EphemeralSecretKey,
491 sha256::Hash,
492 >::with_shared_secrets(
493 num_relays,
494 &routing_info,
495 dest,
496 session_key,
497 &shared_secrets,
498 )
499 .unwrap();
500
501 let bytes = header.to_vec().unwrap();
502 let header_from_bytes = Header::<
503 TestAddress,
504 hmac::sha256::Hmac,
505 chacha20::StreamCipher,
506 x25519_ristretto::EphemeralSecretKey,
507 sha256::Hash,
508 >::from_bytes(&bytes)
509 .unwrap();
510
511 assert_eq!(header_from_bytes.routing_info, header.routing_info);
512 assert_eq!(header_from_bytes.routing_info_mac, header.routing_info_mac);
513 assert_eq!(header_from_bytes.public_key, header.public_key);
514 }
515}