sfynx/
header.rs

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    /// Maximum number of hops per circuit.
31    max_relays: u8,
32
33    /// Routing table for the header.
34    pub routing_info: Vec<u8>,
35
36    /// HMAC of routing_info.
37    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    /// Validate header input data. Panics if data is incorrect.
223    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    /// Process header.
235    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        // checks if there are suffixed zeros in the padding
365        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}